allow ftp sources, remove stray whitespaces and use tabs for indentation
authorMoritz Venn <ritzmo@users.schwerkraft.elitedvb.net>
Mon, 1 Dec 2008 16:50:19 +0000 (16:50 +0000)
committerMoritz Venn <ritzmo@users.schwerkraft.elitedvb.net>
Mon, 1 Dec 2008 16:50:19 +0000 (16:50 +0000)
webcamviewer/src/FTPDownloader.py [new file with mode: 0644]
webcamviewer/src/PictureScreen.py

diff --git a/webcamviewer/src/FTPDownloader.py b/webcamviewer/src/FTPDownloader.py
new file mode 100644 (file)
index 0000000..8c7aeb7
--- /dev/null
@@ -0,0 +1,121 @@
+from twisted.internet import reactor, defer
+from twisted.internet.protocol import Protocol, ClientCreator
+from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
+
+from os import SEEK_END
+
+# XXX: did I ever actually test supportPartial?
+class FTPDownloader(Protocol):
+       """Download to a file from FTP."""
+
+       def __init__(self, host, port, path, fileOrName, username = 'anonymous', \
+               password = 'my@email.com', passive = True, supportPartial = False, \
+               *args, **kwargs):
+
+               timeout = 30
+
+               # We need this later
+               self.path = path
+               self.resume = supportPartial
+
+               # Output
+               if isinstance(fileOrName, str):
+                       self.filename = fileOrName
+                       self.file = None
+               else:
+                       self.file = fileOrName
+
+               creator = ClientCreator(reactor, FTPClient, username, password, passive = passive)
+
+               creator.connectTCP(host, port, timeout).addCallback(self.controlConnectionMade).addErrback(self.connectionFailed)
+
+               self.deferred = defer.Deferred()
+
+       def controlConnectionMade(self, ftpclient):
+               # We need the client locally
+               self.ftpclient = ftpclient
+
+               # Try to fetch filesize
+               self.ftpFetchSize()
+
+       # Handle recieved msg
+       def sizeRcvd(self, msgs):
+               # Split up return
+               code, msg = msgs[0].split()
+               if code == '213':
+                       self.totallength = int(msg)
+                       # We know the size, so start fetching
+                       self.ftpFetchFile()
+               else:
+                       # Error while reading size, try to list it
+                       self.ftpFetchList()
+
+       def ftpFetchSize(self):
+               d = self.ftpclient.queueStringCommand('SIZE ' + self.path)
+               d.addCallback(self.sizeRcvd).addErrback(self.ftpFetchList)
+
+       # Handle recieved msg
+       def listRcvd(self, *args):
+               # Quit if file not found
+               if not len(self.filelist.files):
+                       self.connectionFailed()
+                       return
+
+               self.totallength = self.filelist.files[0]['size']
+
+               # Invalidate list
+               self.filelist = None
+
+               # We know the size, so start fetching
+               self.ftpFetchFile()
+
+       def ftpFetchList(self, *args, **kwargs):
+               self.filelist = FTPFileListProtocol()
+               d = self.ftpclient.list(self.path, self.filelist)
+               d.addCallback(self.listRcvd).addErrback(self.connectionFailed)
+
+       def openFile(self):
+               if self.resume:
+                       file = open(self.filename, 'ab')
+               else:
+                       file = open(self.filename, 'wb')
+
+               return (file, file.tell())
+
+       def ftpFetchFile(self):
+               offset = 0
+
+               # Finally open file
+               if self.file is None:
+                       try:
+                               self.file, offset = self.openFile()
+                       except IOError, ie:
+                               self.connectionFailed()
+                               return
+
+               offset = self.resume and offset or 0
+
+               d = self.ftpclient.retrieveFile(self.path, self, offset = offset)
+               d.addCallback(self.ftpFinish).addErrback(self.connectionFailed)
+
+       def dataReceived(self, data):
+               if not self.file:
+                       return
+
+               try:
+                       self.file.seek(0, SEEK_END)
+
+                       self.file.write(data)
+               except IOError, ie:
+                       self.connectionFailed()
+
+       def ftpFinish(self, code = 0, message = None):
+               self.ftpclient.quit()
+               if self.file is not None:
+                       self.file.close()
+               self.deferred.callback(code)
+
+       def connectionFailed(self, reason = None):
+               if self.file is not None:
+                       self.file.close()
+               self.deferred.errback(reason)
index 9e15f70..38a53aa 100755 (executable)
@@ -7,93 +7,185 @@ from Components.AVSwitch import AVSwitch
 from Components.config import config
 from Components.Pixmap import Pixmap
 from Components.ActionMap import ActionMap
-from twisted.web.client import downloadPage
+
+from FTPDownloader import FTPDownloader
+from twisted.web.client import HTTPDownloader
+from twisted.internet import reactor
+from urlparse import urlparse, urlunparse
+
+def _parse(url, defaultPort = None):
+       url = url.strip()
+       parsed = urlparse(url)
+       scheme = parsed[0]
+       path = urlunparse(('','')+parsed[2:])
+
+       if defaultPort is None:
+               if scheme == 'https':
+                       defaultPort = 443
+               elif scheme == 'ftp':
+                       defaultPort = 21
+               else:
+                       defaultPort = 80
+
+       host, port = parsed[1], defaultPort
+
+       if '@' in host:
+               username, host = host.split('@')
+               if ':' in username:
+                       username, password = username.split(':')
+               else:
+                       password = ""
+       else:
+               username = ""
+               password = ""
+
+       if ':' in host:
+               host, port = host.split(':')
+               port = int(port)
+
+       if path == "":
+               path = "/"
+
+       return scheme, host, port, path, username, password
+
+def download(url, file, contextFactory = None, *args, **kwargs):
+
+       """Download a remote file from http(s) or ftp.
+
+       @param file: path to file on filesystem, or file-like object.
+
+       See HTTPDownloader to see what extra args can be passed if remote file
+       is accessible via http or https. Both Backends should offer supportPartial.
+       """
+       scheme, host, port, path, username, password = _parse(url)
+
+       if scheme == 'ftp':
+               if not (username and password):
+                       username = 'anonymous'
+                       password = 'my@email.com'
+
+               client = FTPDownloader(
+                       host,
+                       port,
+                       path,
+                       file,
+                       username,
+                       password,
+                       *args,
+                       **kwargs
+               )
+               return client.deferred
+
+       # We force username and password here as we lack a satisfying input method
+       if username and password:
+               from base64 import encodestring
+
+               # twisted will crash if we don't rewrite this ;-)
+               url = scheme + '://' + host + ':' + str(port) + path
+
+               basicAuth = encodestring("%s:%s" % (username, password))
+               authHeader = "Basic " + basicAuth.strip()
+               AuthHeaders = {"Authorization": authHeader}
+
+               if kwargs.has_key("headers"):
+                       kwargs["headers"].update(AuthHeaders)
+               else:
+                       kwargs["headers"] = AuthHeaders
+
+       factory = HTTPDownloader(url, file, *args, **kwargs)
+       if scheme == 'https':
+               from twisted.internet import ssl
+               if contextFactory is None:
+                       contextFactory = ssl.ClientContextFactory()
+               reactor.connectSSL(host, port, factory, contextFactory)
+       else:
+               reactor.connectTCP(host, port, factory)
+
+       return factory.deferred
 
 class PictureScreen(Screen):
-    skin = ""
-    prozessing =False # if fetching or converting is active
-    autoreload =False
-    def __init__(self, session,title,filename, slideshowcallback = None,args=0):
-        self.session=session
-        self.slideshowcallback=slideshowcallback
-        self.screentitle = title
-        ##
-        size_w = getDesktop(0).size().width()  
-        size_h = getDesktop(0).size().height()         
-        self.skin = """
-        <screen position="0,0" size="%i,%i" title="%s" flags=\"wfNoBorder\">
-             <widget name="pixmap" position="0,0" size="%i,%i" backgroundColor=\"black\"/>
-        </screen>""" % (size_w,size_h,filename,size_w,size_h)
-        Screen.__init__(self, session)
-        self.filename = filename
-        self["pixmap"] = Pixmap()
-        
-        self["actions"] = ActionMap(["WizardActions", "DirectionActions","ChannelSelectBaseActions","ShortcutActions"], 
-            {
-             "ok": self.do,
-             "back": self.exit,
-             "green":self.AutoReloaderSwitch,
-             }, -1)
-        
-        self.onLayoutFinish.append(self.do)
-        
-    def AutoReloaderSwitch(self):
-        if self.filename.startswith("http") or self.filename.startswith("ftp"):            
-            if self.autoreload is False:
-                self.autoreload = True
-                self.do()
-            else:
-                self.autoreload = False
-            
-    def do(self): 
-        if self.prozessing:
-            pass       
-        elif self.filename.startswith("http") or self.filename.startswith("ftp"):            
-            self.fetchFile(self.filename)
-        else:
-            self.sourcefile = self.filename
-            self.setPicture(self.filename)
-
-    def exit(self):
-        self.cleanUP()
-        self.close()
-
-    def cleanUP(self):
-        try:
-            if os.path.exists("/tmp/loadedfile"):
-                os.remove("/tmp/loadedfile")
-        except:## OSerror??
-            pass
-    
-    def fetchFile(self,url):
-        self.prozessing =True        
-        self.setTitle("loading File")
-        print "fetching URL ",url
-        self.sourcefile = "/tmp/loadedfile"
-        downloadPage(url,self.sourcefile).addCallback(self.fetchFinished).addErrback(self.fetchFailed)
-            
-        
-    def fetchFailed(self,string):
-        print "fetch failed",string
-        self.setTitle( "fetch failed: "+string)
-        
-    def fetchFinished(self,string):
-        print "fetching finished "
-        self.setPicture(self.sourcefile)   
-              
-    def setPicture(self,string):
-        self.setTitle(self.filename.split("/")[-1])        
-        pixmap = loadPic(string,getDesktop(0).size().width(),getDesktop(0).size().height(), AVSwitch().getAspectRatioSetting()/2,1, 0,1)
-        if pixmap is not None:
-            self["pixmap"].instance.setPixmap(pixmap)
-        self.prozessing =False
-        
-        if self.autoreload is True:
-                self.cleanUP()
-                self.do()
-        elif self.slideshowcallback is not None:
-                self.closetimer = eTimer()
-                self.closetimer.timeout.get().append(self.slideshowcallback)
-                print "waiting ",config.plugins.pictureviewer.slideshowtime.value," seconds for next picture"
-                self.closetimer.start(int(config.plugins.pictureviewer.slideshowtime.value))
-        
+       skin = ""
+       prozessing =False # if fetching or converting is active
+       autoreload =False
+       def __init__(self, session,title,filename, slideshowcallback = None,args=0):
+               self.slideshowcallback=slideshowcallback
+               self.screentitle = title
+               ##
+               size_w = getDesktop(0).size().width()
+               size_h = getDesktop(0).size().height()
+               self.skin = """
+               <screen position="0,0" size="%i,%i" title="%s" flags=\"wfNoBorder\">
+                        <widget name="pixmap" position="0,0" size="%i,%i" backgroundColor=\"black\"/>
+               </screen>""" % (size_w,size_h,filename,size_w,size_h)
+               Screen.__init__(self, session)
+               self.filename = filename
+               self["pixmap"] = Pixmap()
+
+               self["actions"] = ActionMap(["WizardActions", "DirectionActions","ChannelSelectBaseActions","ShortcutActions"],
+                       {
+                        "ok": self.do,
+                        "back": self.exit,
+                        "green":self.AutoReloaderSwitch,
+                        }, -1)
+
+               self.onLayoutFinish.append(self.do)
+
+       def AutoReloaderSwitch(self):
+               if self.filename.startswith("http") or self.filename.startswith("ftp"):
+                       if self.autoreload is False:
+                               self.autoreload = True
+                               self.do()
+                       else:
+                               self.autoreload = False
+
+       def do(self):
+               if self.prozessing:
+                       pass
+               elif self.filename.startswith("http") or self.filename.startswith("ftp"):
+                       self.fetchFile(self.filename)
+               else:
+                       self.sourcefile = self.filename
+                       self.setPicture(self.filename)
+
+       def exit(self):
+               self.cleanUP()
+               self.close()
+
+       def cleanUP(self):
+               try:
+                       if os.path.exists("/tmp/loadedfile"):
+                               os.remove("/tmp/loadedfile")
+               except:## OSerror??
+                       pass
+
+       def fetchFile(self,url):
+               self.prozessing =True
+               self.setTitle("loading File")
+               print "fetching URL ",url
+               self.sourcefile = "/tmp/loadedfile"
+               download(url,self.sourcefile).addCallback(self.fetchFinished).addErrback(self.fetchFailed)
+
+       def fetchFailed(self,string):
+               print "fetch failed",string
+               self.setTitle( "fetch failed: "+string)
+
+       def fetchFinished(self,string):
+               print "fetching finished "
+               self.setPicture(self.sourcefile)
+
+       def setPicture(self,string):
+               self.setTitle(self.filename.split("/")[-1])
+               pixmap = loadPic(string,getDesktop(0).size().width(),getDesktop(0).size().height(), AVSwitch().getAspectRatioSetting()/2,1, 0,1)
+               if pixmap is not None:
+                       self["pixmap"].instance.setPixmap(pixmap)
+               self.prozessing =False
+
+               if self.autoreload is True:
+                               self.cleanUP()
+                               self.do()
+               elif self.slideshowcallback is not None:
+                               self.closetimer = eTimer()
+                               self.closetimer.timeout.get().append(self.slideshowcallback)
+                               print "waiting ",config.plugins.pictureviewer.slideshowtime.value," seconds for next picture"
+                               self.closetimer.start(int(config.plugins.pictureviewer.slideshowtime.value))
+