Started refactoring UPnP server

This commit is contained in:
Ziver Koc 2021-09-02 17:35:35 +02:00
parent ef10430d54
commit 8ba1441572
7 changed files with 99 additions and 164 deletions

View file

@ -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{
}

View file

@ -25,28 +25,38 @@
package zutil.net.upnp; package zutil.net.upnp;
import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream; import zutil.net.http.HttpPrintStream;
import zutil.net.ssdp.SSDPServiceInfo;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
/** /**
* This class is a UPnP AV Media Server that handles all the * This class is a UPnP Root Server that can host multiple UPnP services.
* other UPnP services
*
* @author Ziver
*/ */
public class UPnPMediaServer extends UPnPRootDevice{ public class UPnPServer implements HttpPage, SSDPServiceInfo {
public static final String RELATIVE_URL = "upnp/rootdev"; 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 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<UPnPService> services = new ArrayList<>();
public UPnPMediaServer(String location) {
public UPnPServer(String location) {
url = location; url = location;
} }
public void respond(HttpPrintStream out, public void respond(HttpPrintStream out,
HttpHeader headers, HttpHeader headers,
Map<String, Object> session, Map<String, Object> session,
@ -54,7 +64,7 @@ public class UPnPMediaServer extends UPnPRootDevice{
Map<String, String> request) throws IOException { Map<String, String> request) throws IOException {
out.enableBuffering(true); out.enableBuffering(true);
out.setHeader("Content-Type", "text/xml"); out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "text/xml");
out.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); out.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
out.println("<root xmlns=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"); out.println("<root xmlns=\"urn:schemas-upnp-org:service:ContentDirectory:1\">");
@ -62,16 +72,16 @@ public class UPnPMediaServer extends UPnPRootDevice{
out.println(" <major>1</major>"); out.println(" <major>1</major>");
out.println(" <minor>0</minor>"); out.println(" <minor>0</minor>");
out.println(" </specVersion>"); out.println(" </specVersion>");
out.println(" <URLBase>" + url + "</URLBase>");//" + ssdp.getLocation() + " out.println(" <URLBase>" + url + "</URLBase>"); //" + ssdp.getLocation() + "
out.println(" <device>"); out.println(" <device>");
out.println(" <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"); out.println(" <deviceType>" + DEVICE_TYPE + "</deviceType>");
out.println(" <friendlyName>ZupNP AV Media Server</friendlyName>"); out.println(" <friendlyName>" + nameFriendly + "</friendlyName>");
out.println(" <manufacturer>Ziver Koc</manufacturer>"); out.println(" <manufacturer>" + manufacturer + "</manufacturer>");
out.println(" <manufacturerURL>http://ziver.koc.se</manufacturerURL>"); out.println(" <manufacturerURL>" + manufacturerUrl + "</manufacturerURL>");
out.println("");
out.println(" <modelName>ZupNP Server</modelName>"); out.println(" <modelName>ZupNP Server</modelName>");
out.println(" <modelDescription>UPnP AV Media Server</modelDescription>"); out.println(" <modelDescription>" + modelDescription + "</modelDescription>");
out.println(" <modelNumber>0.1</modelNumber>"); out.println(" <modelNumber>" + modelNumber + "</modelNumber>");
out.println("");
out.println(" <UDN>" + getUUID() + "</UDN>"); out.println(" <UDN>" + getUUID() + "</UDN>");
out.println(" <serviceList>"); out.println(" <serviceList>");
out.println(" <service>"); out.println(" <service>");
@ -108,9 +118,6 @@ public class UPnPMediaServer extends UPnPRootDevice{
return getUUID() + "::upnp:rootdevice"; return getUUID() + "::upnp:rootdevice";
} }
public String getUUID() { public String getUUID() {
if (uuid == null) {
uuid = "uuid:" + UUID.nameUUIDFromBytes(this.getClass().toString().getBytes()); //(url+Math.random()).getBytes()
}
return uuid; return uuid;
} }

View file

@ -25,9 +25,7 @@
package zutil.net.upnp; package zutil.net.upnp;
/** /**
* Information about a UPNP Service * A interface that represents a UPnP service.
*
* @author Ziver
*/ */
public interface UPnPService { public interface UPnPService {

View file

@ -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;
}

View file

@ -24,7 +24,10 @@
package zutil.net.upnp.service; 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.io.file.FileUtil;
import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPage; import zutil.net.http.HttpPage;
@ -40,16 +43,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* Information about a UPNP Service * A directory browsing UPnP service.
*
* @author Ziver
*/ */
public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface { public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface {
public static final int VERSION = 1;
private static List<File> file_list; private static List<File> file_list;
public UPnPContentDirectory() {}
public UPnPContentDirectory(File dir) { public UPnPContentDirectory(File dir) {
file_list = FileUtil.search(dir, new LinkedList<>(), true, Integer.MAX_VALUE); file_list = FileUtil.search(dir, new LinkedList<>(), true, Integer.MAX_VALUE);
@ -85,7 +83,8 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface
*/ */
@WSReturnName("Id") @WSReturnName("Id")
public int GetSystemUpdateID() { 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. * in any particular object container.
* *
*/ */
//@WSNameSpace("urn:schemas-upnp-org:service:ContentDirectory:1") @WSPath("urn:schemas-upnp-org:service:ContentDirectory:1")
public BrowseRetObj Browse( public BrowseRetObj Browse(
@WSParamName("ObjectID") String ObjectID, @WSParamName("ObjectID") String objectID,
@WSParamName("BrowseFlag") String BrowseFlag, @WSParamName("BrowseFlag") String browseFlag,
@WSParamName("Filter") String Filter, @WSParamName("filter") String filter,
@WSParamName("StartingIndex") int StartingIndex, @WSParamName("StartingIndex") int startingIndex,
@WSParamName("RequestedCount") int RequestedCount, @WSParamName("RequestedCount") int requestedCount,
@WSParamName("SortCriteria") String SortCriteria) { @WSParamName("SortCriteria") String sortCriteria) {
BrowseRetObj ret = new BrowseRetObj(); BrowseRetObj ret = new BrowseRetObj();
if (BrowseFlag.equals("BrowseMetadata")) {
if (browseFlag.equals("BrowseMetadata")) {
} }
else if (BrowseFlag.equals("BrowseDirectChildren")) { else if (browseFlag.equals("BrowseDirectChildren")) {
StringBuilder xml = new StringBuilder(); Document document = DocumentHelper.createDocument();
xml.append("<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " + Element rootElement = document.addElement("DIDL-Lite", "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/");
"xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" " + rootElement.addNamespace("dc", "http://purl.org/dc/elements/1.1/");
"xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">"); rootElement.addNamespace("upnp", "urn:schemas-upnp-org:metadata-1-0/upnp/");
List<File> tmp = FileUtil.search(file_list.get(Integer.parseInt(ObjectID)), new LinkedList<>(), false);
for (File file : tmp) {
xml.append(" <container id=\"").append(file_list.indexOf(file)).append("\" ");
if (tmp.get(0) != file) xml.append("parentID=\"").append(file_list.indexOf(file.getParent())).append("\" ");
if (file.isDirectory()) xml.append("childCount=\"").append(file.list().length).append("\" ");
xml.append("restricted=\"1\" searchable=\"0\" >");
xml.append(" <dc:title>").append(file.getName()).append("</dc:title> "); List<File> fileList = FileUtil.search(file_list.get(Integer.parseInt(objectID)), new LinkedList<>(), false);
if (file.isDirectory())
xml.append(" <upnp:class>object.container.storageFolder</upnp:class> ");
else
xml.append(" <upnp:class>object.container</upnp:class> ");
xml.append(" <upnp:storageUsed>").append((int) (file.length() / 1000)).append("</upnp:storageUsed> ");
xml.append(" </container> ");
ret.NumberReturned++; for (File file : fileList) {
ret.TotalMatches++; 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("</DIDL-Lite>");
ret.Result = xml.toString(); ret.NumberReturned = fileList.size();
//Document document = DocumentHelper.parseText(xml.toString()); ret.TotalMatches = rootElement.elements().size();
//ret.Result = document.getRootElement(); ret.Result = document.asXML();
} }
return ret; return ret;
} }
public class BrowseRetObj extends WSReturnObject{
public String Result;
public int NumberReturned;
public int TotalMatches;
public int UpdateID;
}
@WSIgnore @WSIgnore
@ -155,7 +152,7 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface
Map<String, String> request) throws IOException { Map<String, String> request) throws IOException {
out.enableBuffering(true); out.enableBuffering(true);
out.setHeader("Content-Type", "text/xml"); out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "text/xml");
out.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); out.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
out.println("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">"); out.println("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">");
@ -608,4 +605,15 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface
out.println(" </actionList>"); out.println(" </actionList>");
out.println("</scpd>"); out.println("</scpd>");
} }
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;
}
} }

View file

@ -76,8 +76,7 @@ public interface WSInterface {
/** /**
* Annotation that assigns a name to an parameters * Annotation will change the exposed name of the annotated field or method input parameter.
* in an method.
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD}) @Target({ElementType.PARAMETER, ElementType.FIELD})

View file

@ -38,7 +38,7 @@ import java.io.IOException;
public class UPnPServerTest { public class UPnPServerTest {
public static void main(String[] args) throws IOException{ 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"); MultiPrintStream.out.println("UPNP Server running");
UPnPContentDirectory cds = new UPnPContentDirectory(new File("C:\\Users\\Ziver\\Desktop\\lan")); UPnPContentDirectory cds = new UPnPContentDirectory(new File("C:\\Users\\Ziver\\Desktop\\lan"));