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;
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<UPnPService> services = new ArrayList<>();
public UPnPMediaServer(String location) {
public UPnPServer(String location) {
url = location;
}
public void respond(HttpPrintStream out,
HttpHeader headers,
Map<String, Object> session,
@ -54,7 +64,7 @@ public class UPnPMediaServer extends UPnPRootDevice{
Map<String, String> request) throws IOException {
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("<root xmlns=\"urn:schemas-upnp-org:service:ContentDirectory:1\">");
@ -62,32 +72,32 @@ public class UPnPMediaServer extends UPnPRootDevice{
out.println(" <major>1</major>");
out.println(" <minor>0</minor>");
out.println(" </specVersion>");
out.println(" <URLBase>" + url + "</URLBase>");//" + ssdp.getLocation() + "
out.println(" <URLBase>" + url + "</URLBase>"); //" + ssdp.getLocation() + "
out.println(" <device>");
out.println(" <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
out.println(" <friendlyName>ZupNP AV Media Server</friendlyName>");
out.println(" <manufacturer>Ziver Koc</manufacturer>");
out.println(" <manufacturerURL>http://ziver.koc.se</manufacturerURL>");
out.println("");
out.println(" <deviceType>" + DEVICE_TYPE + "</deviceType>");
out.println(" <friendlyName>" + nameFriendly + "</friendlyName>");
out.println(" <manufacturer>" + manufacturer + "</manufacturer>");
out.println(" <manufacturerURL>" + manufacturerUrl + "</manufacturerURL>");
out.println(" <modelName>ZupNP Server</modelName>");
out.println(" <modelDescription>UPnP AV Media Server</modelDescription>");
out.println(" <modelNumber>0.1</modelNumber>");
out.println(" <modelDescription>" + modelDescription + "</modelDescription>");
out.println(" <modelNumber>" + modelNumber + "</modelNumber>");
out.println("");
out.println(" <UDN>" + getUUID() + "</UDN>");
out.println(" <serviceList>");
out.println(" <service>");
out.println(" <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>");
out.println(" <serviceId>urn:upnp-org:serviceId:CMGR_1-0</serviceId>");
out.println(" <SCPDURL>CMGR_Control/GetServDesc</SCPDURL>");
out.println(" <controlURL>CMGR_Control</controlURL>");
out.println(" <eventSubURL>CMGR_Event</eventSubURL>");
out.println(" </service>");
out.println(" <service>");
out.println(" <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>");
out.println(" <serviceId>urn:upnp-org:serviceId:CDS_1-0</serviceId>");
out.println(" <SCPDURL>SCP/ContentDir</SCPDURL>");
out.println(" <controlURL>Action/ContentDir</controlURL>");
out.println(" <eventSubURL>Event/ContentDir</eventSubURL>");
out.println(" </service>");
out.println(" <service>");
out.println(" <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>");
out.println(" <serviceId>urn:upnp-org:serviceId:CMGR_1-0</serviceId>");
out.println(" <SCPDURL>CMGR_Control/GetServDesc</SCPDURL>");
out.println(" <controlURL>CMGR_Control</controlURL>");
out.println(" <eventSubURL>CMGR_Event</eventSubURL>");
out.println(" </service>");
out.println(" <service>");
out.println(" <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>");
out.println(" <serviceId>urn:upnp-org:serviceId:CDS_1-0</serviceId>");
out.println(" <SCPDURL>SCP/ContentDir</SCPDURL>");
out.println(" <controlURL>Action/ContentDir</controlURL>");
out.println(" <eventSubURL>Event/ContentDir</eventSubURL>");
out.println(" </service>");
out.println(" </serviceList>");
out.println(" </device>");
out.println("</root>");
@ -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;
}

View file

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

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;
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> 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("<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " +
"xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" " +
"xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">");
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\" >");
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(" <dc:title>").append(file.getName()).append("</dc:title> ");
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> ");
List<File> 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("</DIDL-Lite>");
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<String, String> request) throws IOException {
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("<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("</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
* 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})

View file

@ -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"));