Skip to content

Commit

Permalink
Create main runnable, cleanup code, make it work... OMG IT WORKS!
Browse files Browse the repository at this point in the history
  • Loading branch information
n-i-x committed Feb 4, 2013
1 parent b48a546 commit ca7e074
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 36 deletions.
4 changes: 3 additions & 1 deletion common.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ def LoadOrCreateConfig():

if not config.has_section('AUTOBACKUP'):
config.add_section('AUTOBACKUP')
config.set('AUTOBACKUP', 'uuid', uuid.uuid4())
config.set('AUTOBACKUP', 'backup_dir',
os.path.expanduser('~/PCAutoBackup'))
config.set('AUTOBACKUP', 'default_interface',
socket.gethostbyname(socket.gethostname()))
config.set('AUTOBACKUP', 'server_name', '[PC]AutoBackup')
config.set('AUTOBACKUP', 'uuid', uuid.uuid4())
with open(CONFIG_FILE, 'wb') as config_file:
config.write(config_file)

Expand Down
167 changes: 140 additions & 27 deletions mediaserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,40 @@
__author__ = 'jeff@rebeiro.net (Jeff Rebeiro)'

import HTMLParser
import os
import random
import re
import string

from twisted.internet import reactor
from twisted.web.resource import Resource
from twisted.web.server import Site

import common

CREATE_OBJ = re.compile(r'<u:CreateObject xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">.*<Elements>(.*)</Elements>', re.DOTALL)
CREATE_OBJ_DETAILS = re.compile(r'&lt;dc:title&gt;(.*)&lt;/dc:title&gt;.*protocolInfo="\*:\*:(.*):DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_CI=0"')
CREATE_OBJ = '"urn:schemas-upnp-org:service:ContentDirectory:1#CreateObject"'
CREATE_OBJ_DIDL = re.compile(r'.*<Elements>(?P<didl>.*dc:title&gt;(?P<name>.*)&lt;/dc:title.*dc:date&gt;(?P<date>.*)&lt;/dc:date.*protocolInfo=&quot;\*:\*:(?P<type>.*):DLNA.ORG_PN.*size=&quot;(?P<size>\d+)&quot;.*)</Elements>.*')
CREATE_OBJ_RESPONSE = '''<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:CreateObjectResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<ObjectID>%(obj_id)s</ObjectID>
<Result>&lt;DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp='urn:schemas-upnp-org:metadata-1-0/upnp/' xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/" xmlns:sec="http://www.sec.co.kr/"&gt;&lt;item id="%(obj_id)s" parentID="%(parent_id)s" restricted="0" dlna:dlnaManaged="00000004"&gt;&lt;dc:title&gt;&lt;/dc:title&gt;&lt;res protocolInfo="http-get:*:%(obj_type)s:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=00D00000000000000000000000000000" importUri="http://%(interface)s:52235/cd/content?didx=0_id=%(obj_id)s" dlna:resumeUpload="1" dlna:uploadedSize="0" size="%(obj_size)s"&gt;&lt;/res&gt;&lt;upnp:class&gt;object.item.imageItem&lt;/upnp:class&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</Result>
</u:CreateObjectResponse>
</s:Body>
</s:Envelope>'''

X_BACKUP_DONE = '"urn:schemas-upnp-org:service:ContentDirectory:1#X_BACKUP_DONE"'
X_BACKUP_START = '"urn:schemas-upnp-org:service:ContentDirectory:1#X_BACKUP_START"'

X_BACKUP = re.compile(r'<u:X_BACKUP_(\w+) xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">')
X_BACKUP_RESPONSE = '''<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:X_BACKUP_%sResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1"/>
</s:Body>
</s:Envelope>'''

DMS_DESC = '''<?xml version="1.0"?>
DMS_DESC_RESPONSE = '''<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0" xmlns:sec="http://www.sec.co.kr/dlna" xmlns:dlna="urn:schemas-dlna-org:device-1-0">
<specVersion>
<major>1</major>
Expand Down Expand Up @@ -63,56 +78,154 @@
</root>'''


class Backup(object):

backup_objects = {}

def __init__(self):
self.config = common.LoadOrCreateConfig()

def _GenerateObjectID(self, obj_date, length=10):
chars = string.letters + string.digits
rand_chars = ''.join(random.choice(chars) for i in xrange(length))
parent_id = 'UP_%s' % obj_date
obj_id = '%s_%s' % (parent_id, rand_chars)
return (parent_id, obj_id)

def CreateObject(self, obj_name, obj_date, obj_type, obj_size):
(parent_id, obj_id) = self._GenerateObjectID(obj_date)
self.backup_objects[obj_id] = {'obj_name': obj_name,
'obj_date': obj_date,
'obj_type': obj_type,
'parent_id': parent_id,
'obj_size': obj_size}
return obj_id

def FinishBackup(self):
pass

def GetObjectDetails(self, obj_id):
return self.backup_objects.get(obj_id)

def StartBackup(self):
pass

def WriteObject(self, obj_id, data):
obj_details = self.GetObjectDetails(obj_id)

obj_dir = os.path.join(self.config.get('AUTOBACKUP', 'backup_dir'),
obj_details['obj_date'])

if not os.path.isdir(obj_dir):
os.makedirs(obj_dir)

obj_file = os.path.join(obj_dir, obj_details['obj_name'])

with open(obj_file, 'wb') as f:
f.write(data)

del(self.backup_objects[obj_id])


class MediaServer(Resource):

isLeaf = True

def __init__(self):
def __init__(self, debug=False):
self.config = common.LoadOrCreateConfig()
self.debug = debug

def render_GET(self, request):
if self.debug:
print 'Request headers:'
print request.getAllHeaders()
if request.path == '/DMS/SamsungDmsDesc.xml':
return self.GetDMSDescriptionResponse()
else:
print 'Unhandled GET request: %s' % request.path

def render_POST(self, request):
if self.debug:
print 'Request args:'
print request.args
print 'Request headers:'
print request.getAllHeaders()
if request.path == '/cd/content':
return self.ReceiveUpload(request)
if request.path == '/upnp/control/ContentDirectory1':
return self.GetContentDirectoryResponse(request.content.read())

def GetContentDirectoryResponse(self, content):
if X_BACKUP.search(content):
action = X_BACKUP.search(content).group(1)
response = X_BACKUP_RESPONSE % action
print "Response:"
return self.GetContentDirectoryResponse(request)
else:
print 'Unhandled POST request: %s' % request.path

def GetContentDirectoryResponse(self, request):
soapaction = request.getHeader('soapaction')

response = ''

if soapaction == X_BACKUP_START:
response = X_BACKUP_RESPONSE % 'START'
if self.debug:
print 'Response:'
print response
if soapaction == CREATE_OBJ:
request.content.seek(0)
soap_xml = request.content.read()

m = CREATE_OBJ_DIDL.match(soap_xml)
if m:
obj_name = m.group('name')
obj_date = m.group('date')
obj_type = m.group('type')
obj_size = m.group('size')

backup = Backup()
obj_id = backup.CreateObject(obj_name, obj_date, obj_type, obj_size)
obj_details = backup.GetObjectDetails(obj_id)

response = CREATE_OBJ_RESPONSE % {
'interface': self.config.get('AUTOBACKUP', 'default_interface'),
'obj_id': obj_id,
'obj_type': obj_type,
'obj_size': obj_size,
'parent_id': obj_details['parent_id']}
if soapaction == X_BACKUP_DONE:
response = X_BACKUP_RESPONSE % 'DONE'

if self.debug:
print 'Response:'
print response
if CREATE_OBJECT.search(content):
obj_didl = CREATE_OBJ.search(content).group(1)
obj_details = CREATE_OBJ_DETAILS.search(obj_didl).groups()
obj_name = obj_details[0]
obj_type = obj_details[1]

return response

def GetDMSDescriptionResponse(self):
response = DMS_DESC % {'friendly_name': self.config.get('AUTOBACKUP',
'server_name'),
'uuid': self.config.get('AUTOBACKUP', 'uuid')}
if __name__ == '__main__':
print "Response:"
response = DMS_DESC_RESPONSE % {
'friendly_name': self.config.get('AUTOBACKUP', 'server_name'),
'uuid': self.config.get('AUTOBACKUP', 'uuid')}
if self.debug:
print 'Response:'
print response

return response

def ReceiveUpload(self, request):
response = ''
obj_id = request.args['didx'][0].split('=')[1]
backup = Backup()

data = request.content.read()
backup.WriteObject(obj_id, data)
return response


def StartMediaServer():
resource = MediaServer()
def StartMediaServer(debug=False):
resource = MediaServer(debug=debug)
factory = Site(resource)
reactor.listenTCP(52235, factory)
reactor.run()


def main():
StartMediaServer()
StartMediaServer(debug=True)


if __name__ == "__main__":
if __name__ == '__main__':
main()
41 changes: 41 additions & 0 deletions pc-autobackup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/python
#
# Copyright 2013 Jeff Rebeiro (jeff@rebeiro.net) All rights reserved
# Main runnable for PC Autobackup

__author__ = 'jeff@rebeiro.net (Jeff Rebeiro)'

import optparse

from twisted.internet import reactor
from twisted.web.server import Site

import common
import ssdp
import mediaserver


def main():
parser = optparse.OptionParser()
parser.add_option('-b', '--bind', dest='bind',
help='Bind the server to a specific IP',
metavar='IPADDRESS')
parser.add_option('-d', '--debug', dest='debug', action="store_true",
default=False, help='Print debug information')
(options, args) = parser.parse_args()

config = common.LoadOrCreateConfig()
if options.bind:
config.set('AUTOBACKUP', 'default_interface', options.bind)

#ssdp.StartSSDPServer(debug=options.debug)
#mediaserver.StartMediaServer(debug=options.debug)
resource = mediaserver.MediaServer(debug=options.debug)
factory = Site(resource)
reactor.listenMulticast(1900, ssdp.SSDPServer(debug=options.debug))
reactor.listenTCP(52235, factory)
reactor.run()


if __name__ == '__main__':
main()
16 changes: 8 additions & 8 deletions ssdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@

class SSDPServer(DatagramProtocol):

def __init__(self):
def __init__(self, debug=False):
self.config = common.LoadOrCreateConfig()
self.debug = debug

def startProtocol(self):
self.transport.setTTL(5)
Expand All @@ -37,11 +38,10 @@ def startProtocol(self):
def datagramReceived(self, datagram, address):
m = MSEARCH.match(datagram)
if m:
# TODO(jrebeiro): Make this print in debug mode when the main runnable
# module is created and implements optparse
# TODO(jrebeiro): Verify that MediaServer is the only discovery request
# PCAutoBackup responds to.
print 'Received M-SEARCH for %s from %r' % (m.group(3), address)
if self.debug:
print 'Received M-SEARCH for %s from %r' % (m.group(3), address)
if m.group(3) == 'MediaServer':
self.SendSSDPResponse(address)

Expand All @@ -55,18 +55,18 @@ def SendSSDPResponse(self, address):
'default_interface'),
self.config.get('AUTOBACKUP', 'uuid'))
self.transport.write(response, address)
if __name__ == '__main__':
if self.debug:
print "Response:"
print response


def StartSSDPServer():
reactor.listenMulticast(1900, SSDPServer())
def StartSSDPServer(debug=False):
reactor.listenMulticast(1900, SSDPServer(debug=debug))
reactor.run()


def main():
StartSSDPServer()
StartSSDPServer(debug=True)


if __name__ == "__main__":
Expand Down

0 comments on commit ca7e074

Please sign in to comment.