Incorporate wmcbrine's changes to deliver html for normal web browser plus a mobile...
[pyTivo/wmcbrine/lucasnz.git] / plugin.py
blobfbd3571a04fb3bf64a85c7fb35c698119286b98d
1 import os
2 import random
3 import shutil
4 import sys
5 import threading
6 import time
7 import urllib
9 from Cheetah.Filters import Filter
10 from lrucache import LRUCache
12 if os.path.sep == '/':
13 quote = urllib.quote
14 unquote = urllib.unquote_plus
15 else:
16 quote = lambda x: urllib.quote(x.replace(os.path.sep, '/'))
17 unquote = lambda x: os.path.normpath(urllib.unquote_plus(x))
19 class Error:
20 CONTENT_TYPE = 'text/html'
22 def GetPlugin(name):
23 try:
24 module_name = '.'.join(['plugins', name, name])
25 module = __import__(module_name, globals(), locals(), name)
26 plugin = getattr(module, module.CLASS_NAME)()
27 return plugin
28 except ImportError:
29 print 'Error no', name, 'plugin exists. Check the type ' \
30 'setting for your share.'
31 return Error
33 class EncodeUnicode(Filter):
34 def filter(self, val, **kw):
35 """Encode Unicode strings, by default in UTF-8"""
37 encoding = kw.get('encoding', 'utf8')
39 if type(val) == str:
40 try:
41 val = val.decode('utf8')
42 except:
43 if sys.platform == 'darwin':
44 val = val.decode('macroman')
45 else:
46 val = val.decode('iso8859-1')
47 elif type(val) != unicode:
48 val = str(val)
49 return val.encode(encoding)
51 class Plugin(object):
53 random_lock = threading.Lock()
55 CONTENT_TYPE = ''
57 recurse_cache = LRUCache(5)
58 dir_cache = LRUCache(10)
60 def __new__(cls, *args, **kwds):
61 it = cls.__dict__.get('__it__')
62 if it is not None:
63 return it
64 cls.__it__ = it = object.__new__(cls)
65 it.init(*args, **kwds)
66 return it
68 def init(self):
69 pass
71 def send_file(self, handler, path, query):
72 handler.send_response(200)
73 handler.end_headers()
74 f = open(unicode(path, 'utf-8'), 'rb')
75 shutil.copyfileobj(f, handler.wfile)
76 f.close()
78 def get_local_base_path(self, handler, query):
80 subcname = query['Container'][0]
81 container = handler.server.containers[subcname.split('/')[0]]
83 return os.path.normpath(container['path'])
85 def get_local_path(self, handler, query):
87 subcname = query['Container'][0]
88 container = handler.server.containers[subcname.split('/')[0]]
90 path = os.path.normpath(container['path'])
91 for folder in subcname.split('/')[1:]:
92 if folder == '..':
93 return False
94 path = os.path.join(path, folder)
95 return path
97 def item_count(self, handler, query, cname, files, last_start=0):
98 """Return only the desired portion of the list, as specified by
99 ItemCount, AnchorItem and AnchorOffset. 'files' is either a
100 list of strings, OR a list of objects with a 'name' attribute.
102 def no_anchor(handler, anchor):
103 handler.server.logger.warning('Anchor not found: ' + anchor)
105 totalFiles = len(files)
106 index = 0
108 if totalFiles and 'ItemCount' in query:
109 count = int(query['ItemCount'][0])
111 if 'AnchorItem' in query:
112 bs = '/TiVoConnect?Command=QueryContainer&Container='
113 local_base_path = self.get_local_base_path(handler, query)
115 anchor = query['AnchorItem'][0]
116 if anchor.startswith(bs):
117 anchor = anchor.replace(bs, '/', 1)
118 anchor = unquote(anchor)
119 anchor = anchor.replace(os.path.sep + cname, local_base_path, 1)
120 if not '://' in anchor:
121 anchor = os.path.normpath(anchor)
123 if type(files[0]) == str:
124 filenames = files
125 else:
126 filenames = [x.name for x in files]
127 try:
128 index = filenames.index(anchor, last_start)
129 except ValueError:
130 if last_start:
131 try:
132 index = filenames.index(anchor, 0, last_start)
133 except ValueError:
134 no_anchor(handler, anchor)
135 else:
136 no_anchor(handler, anchor) # just use index = 0
138 if count > 0:
139 index += 1
141 if 'AnchorOffset' in query:
142 index += int(query['AnchorOffset'][0])
144 if count < 0:
145 index = (index + count) % len(files)
146 count = -count
147 files = files[index:index + count]
149 return files, totalFiles, index
151 def get_files(self, handler, query, filterFunction=None, force_alpha=False):
153 class FileData:
154 def __init__(self, name, isdir):
155 self.name = name
156 self.isdir = isdir
157 st = os.stat(unicode(name, 'utf-8'))
158 self.mdate = int(st.st_mtime)
159 self.size = st.st_size
161 class SortList:
162 def __init__(self, files):
163 self.files = files
164 self.unsorted = True
165 self.sortby = None
166 self.last_start = 0
168 def build_recursive_list(path, recurse=True):
169 files = []
170 path = unicode(path, 'utf-8')
171 try:
172 for f in os.listdir(path):
173 if f.startswith('.'):
174 continue
175 f = os.path.join(path, f)
176 isdir = os.path.isdir(f)
177 f = f.encode('utf-8')
178 if recurse and isdir:
179 files.extend(build_recursive_list(f))
180 else:
181 if not filterFunction or filterFunction(f, file_type):
182 files.append(FileData(f, isdir))
183 except:
184 pass
185 return files
187 subcname = query['Container'][0]
188 cname = subcname.split('/')[0]
189 path = self.get_local_path(handler, query)
191 file_type = query.get('Filter', [''])[0]
193 recurse = query.get('Recurse', ['No'])[0] == 'Yes'
195 filelist = []
196 rc = self.recurse_cache
197 dc = self.dir_cache
198 if recurse:
199 if path in rc and rc.mtime(path) + 300 >= time.time():
200 filelist = rc[path]
201 else:
202 updated = os.stat(unicode(path, 'utf-8'))[8]
203 if path in dc and dc.mtime(path) >= updated:
204 filelist = dc[path]
205 for p in rc:
206 if path.startswith(p) and rc.mtime(p) < updated:
207 del rc[p]
209 if not filelist:
210 filelist = SortList(build_recursive_list(path, recurse))
212 if recurse:
213 rc[path] = filelist
214 else:
215 dc[path] = filelist
217 def dir_sort(x, y):
218 if x.isdir == y.isdir:
219 return name_sort(x, y)
220 else:
221 return y.isdir - x.isdir
223 def name_sort(x, y):
224 return cmp(x.name, y.name)
226 def date_sort(x, y):
227 return cmp(y.mdate, x.mdate)
229 sortby = query.get('SortOrder', ['Normal'])[0]
230 if filelist.unsorted or filelist.sortby != sortby:
231 if force_alpha:
232 filelist.files.sort(dir_sort)
233 elif sortby == '!CaptureDate':
234 filelist.files.sort(date_sort)
235 else:
236 filelist.files.sort(name_sort)
238 filelist.sortby = sortby
239 filelist.unsorted = False
241 files = filelist.files[:]
243 # Trim the list
244 files, total, start = self.item_count(handler, query, cname, files,
245 filelist.last_start)
246 if len(files) > 1:
247 filelist.last_start = start
248 return files, total, start