From 8ba1441572ba5a5d7c0038b3fe22f90267aacb90 Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Thu, 2 Sep 2021 17:35:35 +0200 Subject: [PATCH] Started refactoring UPnP server --- src/zutil/net/upnp/UPnPRootDevice.java | 37 ------- .../{UPnPMediaServer.java => UPnPServer.java} | 73 ++++++------ src/zutil/net/upnp/UPnPService.java | 4 +- src/zutil/net/upnp/service/BrowseRetObj.java | 40 ------- .../upnp/service/UPnPContentDirectory.java | 104 ++++++++++-------- src/zutil/net/ws/WSInterface.java | 3 +- test/zutil/net/upnp/UPnPServerTest.java | 2 +- 7 files changed, 99 insertions(+), 164 deletions(-) delete mode 100644 src/zutil/net/upnp/UPnPRootDevice.java rename src/zutil/net/upnp/{UPnPMediaServer.java => UPnPServer.java} (53%) delete mode 100755 src/zutil/net/upnp/service/BrowseRetObj.java diff --git a/src/zutil/net/upnp/UPnPRootDevice.java b/src/zutil/net/upnp/UPnPRootDevice.java deleted file mode 100644 index a6f333e..0000000 --- a/src/zutil/net/upnp/UPnPRootDevice.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2020 Ziver Koc - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package zutil.net.upnp; - -import zutil.net.http.HttpPage; -import zutil.net.ssdp.SSDPServiceInfo; -/** - * This class is a UPnP Server class that will be extended - * by all root devices handles all the other UPnP services - * - * @author Ziver - */ -public abstract class UPnPRootDevice implements HttpPage, SSDPServiceInfo{ - -} diff --git a/src/zutil/net/upnp/UPnPMediaServer.java b/src/zutil/net/upnp/UPnPServer.java similarity index 53% rename from src/zutil/net/upnp/UPnPMediaServer.java rename to src/zutil/net/upnp/UPnPServer.java index 595761d..a0d1ca9 100755 --- a/src/zutil/net/upnp/UPnPMediaServer.java +++ b/src/zutil/net/upnp/UPnPServer.java @@ -25,28 +25,38 @@ package zutil.net.upnp; import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPage; import zutil.net.http.HttpPrintStream; +import zutil.net.ssdp.SSDPServiceInfo; import java.io.IOException; +import java.util.ArrayList; import java.util.Map; import java.util.UUID; /** - * This class is a UPnP AV Media Server that handles all the - * other UPnP services - * - * @author Ziver + * This class is a UPnP Root Server that can host multiple UPnP services. */ -public class UPnPMediaServer extends UPnPRootDevice{ +public class UPnPServer implements HttpPage, SSDPServiceInfo { public static final String RELATIVE_URL = "upnp/rootdev"; + private static final String DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1"; private String url; - private String uuid; + private String nameFriendly = "ZupNp Server"; + private String manufacturer = "Ziver Koc"; + private String manufacturerUrl = "http://example.com"; + private String modelName = nameFriendly; + private String modelDescription = "UPnP AV Media Server"; + private float modelNumber = 1.0f; + private final String uuid = "uuid:" + UUID.nameUUIDFromBytes(this.getClass().toString().getBytes()); + private ArrayList services = new ArrayList<>(); - public UPnPMediaServer(String location) { + + public UPnPServer(String location) { url = location; } + public void respond(HttpPrintStream out, HttpHeader headers, Map session, @@ -54,7 +64,7 @@ public class UPnPMediaServer extends UPnPRootDevice{ Map request) throws IOException { out.enableBuffering(true); - out.setHeader("Content-Type", "text/xml"); + out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "text/xml"); out.println(""); out.println(""); @@ -62,32 +72,32 @@ public class UPnPMediaServer extends UPnPRootDevice{ out.println(" 1"); out.println(" 0"); out.println(" "); - out.println(" " + url + "");//" + ssdp.getLocation() + " + out.println(" " + url + ""); //" + ssdp.getLocation() + " out.println(" "); - out.println(" urn:schemas-upnp-org:device:MediaServer:1"); - out.println(" ZupNP AV Media Server"); - out.println(" Ziver Koc"); - out.println(" http://ziver.koc.se"); - out.println(""); + out.println(" " + DEVICE_TYPE + ""); + out.println(" " + nameFriendly + ""); + out.println(" " + manufacturer + ""); + out.println(" " + manufacturerUrl + ""); out.println(" ZupNP Server"); - out.println(" UPnP AV Media Server"); - out.println(" 0.1"); + out.println(" " + modelDescription + ""); + out.println(" " + modelNumber + ""); + out.println(""); out.println(" " + getUUID() + ""); out.println(" "); - out.println(" "); - out.println(" urn:schemas-upnp-org:service:ConnectionManager:1"); - out.println(" urn:upnp-org:serviceId:CMGR_1-0"); - out.println(" CMGR_Control/GetServDesc"); - out.println(" CMGR_Control"); - out.println(" CMGR_Event"); - out.println(" "); - out.println(" "); - out.println(" urn:schemas-upnp-org:service:ContentDirectory:1"); - out.println(" urn:upnp-org:serviceId:CDS_1-0"); - out.println(" SCP/ContentDir"); - out.println(" Action/ContentDir"); - out.println(" Event/ContentDir"); - out.println(" "); + out.println(" "); + out.println(" urn:schemas-upnp-org:service:ConnectionManager:1"); + out.println(" urn:upnp-org:serviceId:CMGR_1-0"); + out.println(" CMGR_Control/GetServDesc"); + out.println(" CMGR_Control"); + out.println(" CMGR_Event"); + out.println(" "); + out.println(" "); + out.println(" urn:schemas-upnp-org:service:ContentDirectory:1"); + out.println(" urn:upnp-org:serviceId:CDS_1-0"); + out.println(" SCP/ContentDir"); + out.println(" Action/ContentDir"); + out.println(" Event/ContentDir"); + out.println(" "); out.println(" "); out.println(" "); out.println(""); @@ -108,9 +118,6 @@ public class UPnPMediaServer extends UPnPRootDevice{ return getUUID() + "::upnp:rootdevice"; } public String getUUID() { - if (uuid == null) { - uuid = "uuid:" + UUID.nameUUIDFromBytes(this.getClass().toString().getBytes()); //(url+Math.random()).getBytes() - } return uuid; } diff --git a/src/zutil/net/upnp/UPnPService.java b/src/zutil/net/upnp/UPnPService.java index c2af586..64c9dc1 100644 --- a/src/zutil/net/upnp/UPnPService.java +++ b/src/zutil/net/upnp/UPnPService.java @@ -25,9 +25,7 @@ package zutil.net.upnp; /** - * Information about a UPNP Service - * - * @author Ziver + * A interface that represents a UPnP service. */ public interface UPnPService { diff --git a/src/zutil/net/upnp/service/BrowseRetObj.java b/src/zutil/net/upnp/service/BrowseRetObj.java deleted file mode 100755 index c20d1f5..0000000 --- a/src/zutil/net/upnp/service/BrowseRetObj.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2020 Ziver Koc - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package zutil.net.upnp.service; - -import zutil.net.ws.WSInterface.WSParamName; -import zutil.net.ws.WSReturnObject; - - -public class BrowseRetObj extends WSReturnObject{ - @WSParamName("Result") - public String Result; - @WSParamName("NumberReturned") - public int NumberReturned; - @WSParamName("TotalMatches") - public int TotalMatches; - @WSParamName("UpdateID") - public int UpdateID; -} diff --git a/src/zutil/net/upnp/service/UPnPContentDirectory.java b/src/zutil/net/upnp/service/UPnPContentDirectory.java index ef93840..f7cb004 100755 --- a/src/zutil/net/upnp/service/UPnPContentDirectory.java +++ b/src/zutil/net/upnp/service/UPnPContentDirectory.java @@ -24,7 +24,10 @@ package zutil.net.upnp.service; -import org.dom4j.DocumentException; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.io.XMLWriter; import zutil.io.file.FileUtil; import zutil.net.http.HttpHeader; import zutil.net.http.HttpPage; @@ -40,16 +43,11 @@ import java.util.List; import java.util.Map; /** - * Information about a UPNP Service - * - * @author Ziver + * A directory browsing UPnP service. */ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface { - public static final int VERSION = 1; - private static List file_list; - public UPnPContentDirectory() {} public UPnPContentDirectory(File dir) { file_list = FileUtil.search(dir, new LinkedList<>(), true, Integer.MAX_VALUE); @@ -85,7 +83,8 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface */ @WSReturnName("Id") public int GetSystemUpdateID() { - return 0; + // Todo: add caching support + return (int) (Math.random() * Integer.MAX_VALUE); } /** @@ -96,56 +95,54 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface * in any particular object container. * */ - //@WSNameSpace("urn:schemas-upnp-org:service:ContentDirectory:1") + @WSPath("urn:schemas-upnp-org:service:ContentDirectory:1") public BrowseRetObj Browse( - @WSParamName("ObjectID") String ObjectID, - @WSParamName("BrowseFlag") String BrowseFlag, - @WSParamName("Filter") String Filter, - @WSParamName("StartingIndex") int StartingIndex, - @WSParamName("RequestedCount") int RequestedCount, - @WSParamName("SortCriteria") String SortCriteria) { + @WSParamName("ObjectID") String objectID, + @WSParamName("BrowseFlag") String browseFlag, + @WSParamName("filter") String filter, + @WSParamName("StartingIndex") int startingIndex, + @WSParamName("RequestedCount") int requestedCount, + @WSParamName("SortCriteria") String sortCriteria) { BrowseRetObj ret = new BrowseRetObj(); - if (BrowseFlag.equals("BrowseMetadata")) { + + if (browseFlag.equals("BrowseMetadata")) { } - else if (BrowseFlag.equals("BrowseDirectChildren")) { - StringBuilder xml = new StringBuilder(); - xml.append(""); - List tmp = FileUtil.search(file_list.get(Integer.parseInt(ObjectID)), new LinkedList<>(), false); - for (File file : tmp) { - xml.append(" "); + else if (browseFlag.equals("BrowseDirectChildren")) { + Document document = DocumentHelper.createDocument(); + Element rootElement = document.addElement("DIDL-Lite", "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"); + rootElement.addNamespace("dc", "http://purl.org/dc/elements/1.1/"); + rootElement.addNamespace("upnp", "urn:schemas-upnp-org:metadata-1-0/upnp/"); - xml.append(" ").append(file.getName()).append(" "); - if (file.isDirectory()) - xml.append(" object.container.storageFolder "); - else - xml.append(" object.container "); - xml.append(" ").append((int) (file.length() / 1000)).append(" "); - xml.append(" "); + List fileList = FileUtil.search(file_list.get(Integer.parseInt(objectID)), new LinkedList<>(), false); - ret.NumberReturned++; - ret.TotalMatches++; + for (File file : fileList) { + Element containerElement = rootElement.addElement("container") + .addAttribute("id", "" + file_list.indexOf(file)) + .addAttribute("restricted", "1") + .addAttribute("searchable", "0"); + + if (fileList.get(0) != file) + containerElement.addAttribute("parentID", "" + file_list.indexOf(file.getParent())); + + containerElement.addElement("dc:title").setText(file.getName()); + containerElement.addElement("upnp:storageUsed").setText("" + (int) (file.length() / 1000)); + + if (file.isDirectory()) { + containerElement.addAttribute("childCount", "" + file.list().length); + containerElement.addElement("upnp:class").setText("object.container.storageFolder"); + } else { + containerElement.addElement("upnp:class").setText("object.container"); + } } - xml.append(""); - ret.Result = xml.toString(); - //Document document = DocumentHelper.parseText(xml.toString()); - //ret.Result = document.getRootElement(); + ret.NumberReturned = fileList.size(); + ret.TotalMatches = rootElement.elements().size(); + ret.Result = document.asXML(); } return ret; } - public class BrowseRetObj extends WSReturnObject{ - public String Result; - public int NumberReturned; - public int TotalMatches; - public int UpdateID; - } @WSIgnore @@ -155,7 +152,7 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface Map request) throws IOException { out.enableBuffering(true); - out.setHeader("Content-Type", "text/xml"); + out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "text/xml"); out.println(""); out.println(""); @@ -608,4 +605,15 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface out.println(" "); out.println(""); } -} + + public static class BrowseRetObj extends WSReturnObject { + @WSParamName("Result") + public String Result; + @WSParamName("NumberReturned") + public int NumberReturned; + @WSParamName("TotalMatches") + public int TotalMatches; + @WSParamName("UpdateID") + public int updateID; + } +} \ No newline at end of file diff --git a/src/zutil/net/ws/WSInterface.java b/src/zutil/net/ws/WSInterface.java index 3523a19..e382ae6 100755 --- a/src/zutil/net/ws/WSInterface.java +++ b/src/zutil/net/ws/WSInterface.java @@ -76,8 +76,7 @@ public interface WSInterface { /** - * Annotation that assigns a name to an parameters - * in an method. + * Annotation will change the exposed name of the annotated field or method input parameter. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) diff --git a/test/zutil/net/upnp/UPnPServerTest.java b/test/zutil/net/upnp/UPnPServerTest.java index df47b8d..0bf68da 100755 --- a/test/zutil/net/upnp/UPnPServerTest.java +++ b/test/zutil/net/upnp/UPnPServerTest.java @@ -38,7 +38,7 @@ import java.io.IOException; public class UPnPServerTest { public static void main(String[] args) throws IOException{ - UPnPMediaServer upnp = new UPnPMediaServer("http://192.168.0.60:8080/"); + UPnPServer upnp = new UPnPServer("http://192.168.0.60:8080/"); MultiPrintStream.out.println("UPNP Server running"); UPnPContentDirectory cds = new UPnPContentDirectory(new File("C:\\Users\\Ziver\\Desktop\\lan"));