PageRenderTime 24ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/XBMCSkins/lib/cachedhttp-v13/cachedhttp.py

http://xbmc-scripting.googlecode.com/
Python | 377 lines | 331 code | 33 blank | 13 comment | 62 complexity | 99a5ccbc26ccbf3cca832fbee3e1b046 MD5 | raw file
Possible License(s): BSD-2-Clause
  1. #ScriptName : cachedhttp (formerly cachemanager)
  2. Version = '1.3'
  3. # Author : Van der Phunck aka Aslak Grinsted. as@phunck.cmo <- not cmo but com
  4. # Desc : cache manager
  5. #
  6. #
  7. import sys, traceback, os.path, re
  8. ScriptPath = os.path.split(sys.modules['cachedhttp'].__file__)[0]+'\\' #this should always work! (note... os.getcwd() doesn't)
  9. sys.path.insert(0, ScriptPath+'ClientCookie.zip')
  10. import urllib2, re, time, string, random
  11. import socket,md5,urlparse
  12. import xbmc,xbmcgui
  13. import mimetypes
  14. import httplib
  15. import ClientCookie
  16. import base64
  17. import urllib
  18. import shutil
  19. Debug=True
  20. DefaultCacheTime=24*60.0 #minutes
  21. try: Emulating = xbmcgui.Emulating #Thanks alot to alexpoet for the xbmc.py,xmbcgui.py emulator. Very useful!
  22. except: Emulating = False
  23. def fileExists(fname):
  24. return os.path.exists(fname)
  25. def readCacheMeta(fname):
  26. if not fileExists(fname): return None
  27. try:
  28. fid=file(fname,'rb')
  29. info=httplib.HTTPMessage(fid)
  30. fid.close()
  31. return info
  32. except:
  33. return None
  34. def isInFolder(fname,folder):
  35. fname=os.path.split(fname)
  36. fnamefolder=fname[0].lower()+'\\'
  37. if not (fnamefolder[-1]=='\\'): fnamefolder=fnamefolder+'\\'
  38. return (fnamefolder==folder.lower())
  39. Rauth=re.compile('^(.*):(.*)@(.*)$',re.IGNORECASE)
  40. def parseAuthUrl(url):
  41. fields=urlparse.urlparse(url)
  42. fields=[fields[0],fields[1],fields[2],fields[3],fields[4],fields[5]]
  43. fauth=Rauth.findall(fields[1])
  44. if len(fauth)==0: return [url,'','']
  45. fields[1]=fauth[0][2] #host
  46. name=urllib.unquote(fauth[0][0]) #username
  47. pwd=urllib.unquote(fauth[0][1]) #password
  48. url=urlparse.urlunparse(fields)
  49. return [url,name,pwd]
  50. Rwhitespace=re.compile('\s\s+',re.IGNORECASE)
  51. Rillegalchars=re.compile('[^\.\_\w\d-]',re.IGNORECASE)
  52. Rfilename=re.compile('[^\\/]*$',re.IGNORECASE)
  53. Rdot2=re.compile('[\.\s]+',re.IGNORECASE)
  54. def urltoxfilename(url):
  55. fields=urlparse.urlparse(url)
  56. fname=Rfilename.findall(fields[2])
  57. if len(fname)==0: fname=['']
  58. fname=fname[0]
  59. if len(fname)==0:
  60. fname=Rfilename.findall(fields[1])
  61. fname=fname[0]
  62. fname=fname.replace('.','-')
  63. fname=fname.replace(',',' ')
  64. fname=Rillegalchars.sub(' ',fname)
  65. fname=Rwhitespace.sub(' ',fname)
  66. fname=Rdot2.sub('.',fname)
  67. if len(fname)>40: #i think 42 is the limit?
  68. ext=os.path.splitext(fname)[1]
  69. fname=fname[0:40-max(len(ext),7)] #7 because a different extension might be added because of the mimetype...
  70. if fname[-1]=='.': fname=fname[0:-1]
  71. fname=fname+ext
  72. return fname
  73. class CustomHandler(ClientCookie.HTTPCookieProcessor):
  74. def http_error_304(self, req, fp, code, message, headers):
  75. addinfourl = urllib2.addinfourl(fp, headers, req.get_full_url())
  76. addinfourl.code = code
  77. return addinfourl
  78. class CachedHTTP:
  79. def __init__(self):
  80. self.userAgent='OobaCacheMgr/'+Version
  81. self.urlContext=''
  82. self.socketTimeout=7.0
  83. self.cacheFolder='Z:\\~HttpCache\\'
  84. if Emulating: self.cacheFolder=ScriptPath+'Cache\\'
  85. try:
  86. os.makedirs(self.cacheFolder)
  87. except: pass
  88. self.cookiefile=self.cacheFolder+'~cookies.txt'
  89. #the tilde is there to ensure that url2xfilename doesn't create a file that might overwrite this
  90. self.defaultCachetime=24*60.0 #minutes
  91. self.cookies=ClientCookie.LWPCookieJar()
  92. try:
  93. self.cookies.revert(self.cookiefile)
  94. except:
  95. print('Could not open cookie file: '+self.cookiefile)
  96. hh=CustomHandler(self.cookies)
  97. self.opener=ClientCookie.build_opener(hh)
  98. def saveCookies(self):
  99. self.cookies.save(self.cookiefile)
  100. def getCookieJar(self): return self.cookies
  101. def getUserAgent(self): return self.userAgent
  102. def setUserAgent(self,val): self.userAgent=val
  103. def getSocketTimeout(self): return self.socketTimeout
  104. def setSocketTimeout(self,val): self.socketTimeout=float(val)
  105. def getCacheFolder(self): return self.cacheFolder
  106. # def setCacheFolder(self,val): #you shouldn't call this really
  107. # self.cacheFolder=str(val)
  108. # if not (self.cacheFolder[-1]=='\\'): self.cacheFolder=self.cacheFolder+'\\'
  109. def getUrlContext(self): return self.urlContext
  110. def setUrlContext(self,val): self.urlContext=str(val)
  111. def getDefaultCachetime(self): return self.defaultCachetime
  112. def setDefaultCachetime(self,val): self.defaultCachetime=float(val)
  113. def getFullUrl(self,url):
  114. if len(url)>0:
  115. if url[0]=='?':
  116. f=urlparse.urlparse(self.urlContext)
  117. baseurl=urlparse.urlunparse([f[0],f[1],f[2],'','',''])
  118. return (baseurl+url).encode('iso-8859-1')
  119. return urlparse.urljoin(self.urlContext,url).encode('iso-8859-1')
  120. def onDataRetrieved(self, bytesRead, totalSize, url, localfile):
  121. return True
  122. def onDownloadFinished(self,success):
  123. pass
  124. def url2cachemetafile(self,url): #returns the filename of the meta file associated with url ... Note it does not necessarily exist
  125. m = md5.new()
  126. m.update(url)
  127. digest=m.hexdigest()
  128. return self.cacheFolder+'~'+digest
  129. def cacheFilename(self,url):
  130. urlmetafile=self.url2cachemetafile(url)
  131. if fileExists(urlmetafile):
  132. info=readCacheMeta(urlmetafile)
  133. localfile=info['CM-Localfile']
  134. if fileExists(localfile): return localfile
  135. return ''
  136. def flushCache(self,url):
  137. urlmetafile=self.url2cachemetafile(url)
  138. if not fileExists(urlmetafile): return
  139. info=readCacheMeta(urlmetafile)
  140. os.remove(urlmetafile)
  141. if info is None: return #couldn't understand meta file...
  142. try:
  143. localfile=info['CM-localfile']
  144. if fileExists(localfile):
  145. if isInFolder(localfile,self.cacheFolder):
  146. os.remove(localfile)
  147. except: pass
  148. def getCacheMeta(self,urlmetafile,expiretime=None): #returns None if the file doesn't exist or if the data is too old.
  149. #expiretime is a way of overriding cachetime information in the metafile...
  150. if not fileExists(urlmetafile): return None
  151. info=readCacheMeta(urlmetafile)
  152. isMetaOK=not (info is None)
  153. try:
  154. if isMetaOK:
  155. localfile=info['CM-localfile']
  156. timestamp=float(info['CM-TimeStamp'])
  157. cachetime=float(info['CM-CacheTime'])
  158. if not expiretime is None: cachetime=expiretime
  159. if cachetime>=0: #(that is not permanent)
  160. isMetaOK=isMetaOK & (abs(time.time()-timestamp)<(cachetime*60))
  161. if fileExists(localfile):
  162. contentlength=int(info['Content-Length'])
  163. isMetaOK=isMetaOK & (os.path.getsize(localfile)==contentlength)
  164. if (not isMetaOK) and isInFolder(localfile,self.cacheFolder):
  165. os.remove(localfile)
  166. else:
  167. isMetaOK=False
  168. except:
  169. isMetaOK=False
  170. if not isMetaOK:
  171. os.remove(urlmetafile)
  172. return None
  173. return info
  174. def cleanCache(self,expiretime=None):
  175. files=os.listdir(self.cacheFolder)
  176. Rmetafile=re.compile('^\~[\dabcdef]{32}$',re.IGNORECASE)
  177. for filename in files:
  178. try:
  179. fname=filename.lower()
  180. if Rmetafile.match(fname):
  181. filename=self.cacheFolder+fname
  182. info=self.getCacheMeta(filename,expiretime)
  183. except:
  184. pass
  185. def urlretrieve(self,url,cachetime=None,localfile=None,ext=None,postdata=None):
  186. url=self.getFullUrl(url)
  187. urlmetafile=self.url2cachemetafile(url)
  188. if cachetime is None: cachetime=self.defaultCachetime
  189. metainfo = self.getCacheMeta(urlmetafile)
  190. furl=None
  191. fcache=None
  192. isDownloadCompleted=False
  193. try:
  194. self.onDataRetrieved(0, None, url, '')
  195. oldtimeout=socket.getdefaulttimeout()
  196. socket.setdefaulttimeout(self.socketTimeout)
  197. authurl=parseAuthUrl(url)
  198. if len(authurl[1])>0: #todo use HTTPBasicAuthHandler instead....
  199. url=authurl[0]
  200. base64string=base64.encodestring('%s:%s' % (authurl[1],authurl[2]))[:-1]
  201. authheader = "Basic %s" % base64string
  202. request = ClientCookie.Request(url)
  203. request.add_header('User-Agent',self.userAgent)
  204. if len(authurl[1])>0: request.add_header("Authorization", authheader)
  205. #if len(self.urlContext)>0: #TODO: not always
  206. # request.add_header('Referer',self.urlContext)
  207. request.add_header('Referer',url.split( '?' )[0])
  208. request.add_header('Cookie','clientSupportsCookies=YES') # ClientCookie doesn't set this?
  209. if not (metainfo is None):
  210. try:
  211. etag=metainfo['ETag']
  212. request.add_header('If-None-Match', etag)
  213. except: pass
  214. try:
  215. lastmodified = metainfo['Last-Modified']
  216. request.add_header('If-Modified-Since', lastmodified)
  217. except: pass
  218. furl=self.opener.open(request,postdata)
  219. info=furl.info()
  220. if not (metainfo is None):
  221. if hasattr(furl, 'code') and furl.code == 304:
  222. self.urlContext=metainfo['CM-UrlContext']
  223. temp=os.path.split(metainfo['CM-Localfile'])
  224. print('using cache: '+temp[1])
  225. isDownloadCompleted=True
  226. if not (localfile is None):
  227. nameext=os.path.splitext(metainfo['CM-Localfile'])
  228. if not (localfile.lower()==nameext[0].lower()):
  229. localfile=localfile+nameext[1]
  230. shutil.copyfile(metainfo['CM-Localfile'],localfile)
  231. return localfile
  232. return metainfo['CM-Localfile']
  233. else:
  234. self.flushCache(url)
  235. try:
  236. totalSize=int(info['Content-Length'])
  237. except:
  238. totalSize=None
  239. #------------ construct local file name ---------
  240. xfname=os.path.splitext(urltoxfilename(url)) #tuple: suggested (filename,ext)
  241. xfname=[xfname[0],xfname[1]] #otherwise you cannot write to it
  242. try:
  243. try:
  244. old_xfname = xfname
  245. filename = info['Content-Disposition'].split( '\"' )[1]
  246. xfname = os.path.splitext( filename )
  247. except:
  248. xfname[0] = old_xfname[0]
  249. mimetype=info['Content-Type'].split(';') # also understand "Content-Type: text/html; charset=utf-8"
  250. mimetype=mimetype[0].strip()
  251. mimeext=mimetypes.guess_extension(mimetype)
  252. if (not (mimeext is None)) and (len(mimeext)>0):
  253. if mimeext=='.m1v': mimeext='.mpg'
  254. xfname[1]=mimeext #override the one based on url alone
  255. except:
  256. pass
  257. if not (ext is None): xfname[1]=ext #override with manual extension
  258. ext=xfname[1]
  259. xfname=xfname[0]
  260. if len(ext)>0:
  261. if not (ext[0]=='.'): ext='.'+ext
  262. ext=ext[0:7] #do not allow so long extensions... Just truncate
  263. if localfile is None: #then autogenerate a file name for the cache
  264. localfile=self.cacheFolder+xfname+ext
  265. i=1
  266. while fileExists(localfile):
  267. i=min(i*10,100000) #add a random number to minimize fileexist checks
  268. localfile=self.cacheFolder+xfname[0:30]+'['+str(random.randint(0,i-1))+']'+ext
  269. else:
  270. localfile=localfile+ext
  271. #------------------------------------------------
  272. fcache=file(localfile,'wb')
  273. iscanceled=not self.onDataRetrieved(0, totalSize, url, localfile)
  274. data='...'
  275. blockSize=8192
  276. pval=0
  277. while len(data)>0:
  278. if not totalSize is None:
  279. if pval>totalSize: totalSize=pval*2
  280. data = furl.read(blockSize)
  281. pval=pval+len(data)
  282. if len(data)>0: fcache.write(data)
  283. if len(data)<blockSize: break
  284. iscanceled=not self.onDataRetrieved(pval, totalSize, url, localfile)
  285. if iscanceled: break
  286. isDownloadCompleted=not iscanceled
  287. self.urlContext=furl.url
  288. finally:
  289. self.onDownloadFinished(isDownloadCompleted)
  290. try:
  291. if not fcache is None: fcache.close()
  292. if not furl is None: furl.close()
  293. socket.setdefaulttimeout(oldtimeout)
  294. if not isDownloadCompleted: os.remove(localfile)
  295. except:
  296. pass
  297. if not isDownloadCompleted:
  298. return None
  299. #------------- write url meta file ------------
  300. #TODO: maybe do something if info['cache-control']=private?
  301. info['Content-Length']=str(pval)
  302. info['CM-Localfile']=localfile
  303. info['CM-urlContext']=self.urlContext
  304. info['CM-CacheTime']=str(cachetime)
  305. info['CM-TimeStamp']=str(time.time())
  306. info['CM-url']=url
  307. fuc=file(urlmetafile,'wb')
  308. fuc.write(str(info))
  309. fuc.close()
  310. return localfile
  311. def urlopen(self,url,data=None):
  312. localfile=self.urlretrieve(url,postdata=data)
  313. f=file(localfile, 'rb')
  314. data = f.read()
  315. f.close()
  316. return data
  317. class CachedHTTPWithProgress(CachedHTTP):
  318. def onDataRetrieved(self, bytesRead, totalSize, url, localfile):
  319. if (not hasattr(self,'progressbar')): self.progressbar=None
  320. if self.progressbar is None:
  321. self.progressbar=xbmcgui.DialogProgress()
  322. self.progressbar.create("Downloading...",url,"> "+localfile)
  323. if (not (bytesRead is None))&(not (totalSize is None)):
  324. pct=int((bytesRead % (totalSize+1))*100.0/totalSize)
  325. self.progressbar.update(pct,url,'> '+os.path.split(localfile)[1])
  326. return not self.progressbar.iscanceled()
  327. def onDownloadFinished(self,success):
  328. try:
  329. self.progressbar.close()
  330. except: pass
  331. self.progressbar=None