Merge commit 'wmcbrine/master'
[pyTivo/TheBayer.git] / plugin.py
blobd86e831a5305d1a4e708c26e5a13bdc9741f6305
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 def GetPluginPath(name):
34 try:
35 module_name = '.'.join(['plugins', name, name])
36 module = __import__(module_name, globals(), locals(), name)
37 return getattr(module, 'SCRIPTDIR')
38 except ImportError:
39 return None
41 class EncodeUnicode(Filter):
42 def filter(self, val, **kw):
43 """Encode Unicode strings, by default in UTF-8"""
45 encoding = kw.get('encoding', 'utf8')
47 if type(val) == str:
48 try:
49 val = val.decode('utf8')
50 except:
51 if sys.platform == 'darwin':
52 val = val.decode('macroman')
53 else:
54 val = val.decode('iso8859-1')
55 elif type(val) != unicode:
56 val = str(val)
57 return val.encode(encoding)
59 class Plugin(object):
61 random_lock = threading.Lock()
63 CONTENT_TYPE = ''
65 recurse_cache = LRUCache(5)
66 dir_cache = LRUCache(10)
68 def __new__(cls, *args, **kwds):
69 it = cls.__dict__.get('__it__')
70 if it is not None:
71 return it
72 cls.__it__ = it = object.__new__(cls)
73 it.init(*args, **kwds)
74 return it
76 def init(self):
77 pass
79 def send_file(self, handler, path, query):
80 handler.send_response(200)
81 handler.end_headers()
82 f = open(path, 'rb')
83 shutil.copyfileobj(f, handler.wfile)
84 f.close()
86 def get_local_base_path(self, handler, query):
88 subcname = query['Container'][0]
89 container = handler.server.containers[subcname.split('/')[0]]
91 return os.path.normpath(container['path'])
93 def get_local_path(self, handler, query):
95 subcname = query['Container'][0]
96 container = handler.server.containers[subcname.split('/')[0]]
98 path = os.path.normpath(container['path'])
99 for folder in subcname.split('/')[1:]:
100 if folder == '..':
101 return False
102 path = os.path.join(path, folder)
103 return path
105 def item_count(self, handler, query, cname, files, last_start=0):
106 """Return only the desired portion of the list, as specified by
107 ItemCount, AnchorItem and AnchorOffset. 'files' is either a
108 list of strings, OR a list of objects with a 'name' attribute.
110 def no_anchor(handler, anchor):
111 handler.server.logger.warning('Anchor not found: ' + anchor)
113 totalFiles = len(files)
114 index = 0
116 if totalFiles and 'ItemCount' in query:
117 count = int(query['ItemCount'][0])
119 if 'AnchorItem' in query:
120 bs = '/TiVoConnect?Command=QueryContainer&Container='
121 local_base_path = self.get_local_base_path(handler, query)
123 anchor = query['AnchorItem'][0]
124 if anchor.startswith(bs):
125 anchor = anchor.replace(bs, '/', 1)
126 anchor = unquote(anchor)
127 anchor = anchor.replace(os.path.sep + cname, local_base_path, 1)
128 if not '://' in anchor:
129 anchor = os.path.normpath(anchor)
131 if type(files[0]) == str:
132 filenames = files
133 else:
134 filenames = [x.name for x in files]
135 try:
136 index = filenames.index(anchor, last_start)
137 except ValueError:
138 if last_start:
139 try:
140 index = filenames.index(anchor, 0, last_start)
141 except ValueError:
142 no_anchor(handler, anchor)
143 else:
144 no_anchor(handler, anchor) # just use index = 0
146 if count > 0:
147 index += 1
149 if 'AnchorOffset' in query:
150 index += int(query['AnchorOffset'][0])
152 if count < 0:
153 index = (index + count) % len(files)
154 count = -count
155 files = files[index:index + count]
157 return files, totalFiles, index
159 def get_files(self, handler, query, filterFunction=None, force_alpha=False):
161 class FileData:
162 def __init__(self, name, isdir):
163 self.name = name
164 self.isdir = isdir
165 st = os.stat(name)
166 self.mdate = int(st.st_mtime)
168 class SortList:
169 def __init__(self, files):
170 self.files = files
171 self.unsorted = True
172 self.sortby = None
173 self.last_start = 0
175 def build_recursive_list(path, recurse=True):
176 files = []
177 try:
178 for f in os.listdir(path):
179 if f.startswith('.'):
180 continue
181 f = os.path.join(path, f)
182 isdir = os.path.isdir(f)
183 if recurse and isdir:
184 files.extend(build_recursive_list(f))
185 else:
186 if not filterFunction or filterFunction(f, file_type):
187 files.append(FileData(f, isdir))
188 except:
189 pass
190 return files
192 subcname = query['Container'][0]
193 cname = subcname.split('/')[0]
194 path = self.get_local_path(handler, query)
196 file_type = query.get('Filter', [''])[0]
198 recurse = query.get('Recurse', ['No'])[0] == 'Yes'
200 filelist = []
201 rc = self.recurse_cache
202 dc = self.dir_cache
203 if recurse:
204 if path in rc and rc.mtime(path) + 300 >= time.time():
205 filelist = rc[path]
206 else:
207 updated = os.stat(path)[8]
208 if path in dc and dc.mtime(path) >= updated:
209 filelist = dc[path]
210 for p in rc:
211 if path.startswith(p) and rc.mtime(p) < updated:
212 del rc[p]
214 if not filelist:
215 filelist = SortList(build_recursive_list(path, recurse))
217 if recurse:
218 rc[path] = filelist
219 else:
220 dc[path] = filelist
222 def dir_sort(x, y):
223 if x.isdir == y.isdir:
224 return name_sort(x, y)
225 else:
226 return y.isdir - x.isdir
228 def name_sort(x, y):
229 return cmp(x.name, y.name)
231 def date_sort(x, y):
232 return cmp(y.mdate, x.mdate)
234 sortby = query.get('SortOrder', ['Normal'])[0]
235 if filelist.unsorted or filelist.sortby != sortby:
236 if force_alpha:
237 filelist.files.sort(dir_sort)
238 elif sortby == '!CaptureDate':
239 filelist.files.sort(date_sort)
240 else:
241 filelist.files.sort(name_sort)
243 filelist.sortby = sortby
244 filelist.unsorted = False
246 files = filelist.files[:]
248 # Trim the list
249 files, total, start = self.item_count(handler, query, cname, files,
250 filelist.last_start)
251 if len(files) > 1:
252 filelist.last_start = start
253 return files, total, start