Still need to skip "_tivo_4K" sections when building list of found TiVos.
[pyTivo/wmcbrine.git] / plugin.py
bloba25235225d13da466d781ae16edd684d931a0e28
1 import os
2 import random
3 import shutil
4 import sys
5 import threading
6 import time
7 import unicodedata
8 import urllib
10 from Cheetah.Filters import Filter
11 from lrucache import LRUCache
13 if os.path.sep == '/':
14 quote = urllib.quote
15 unquote = urllib.unquote_plus
16 else:
17 quote = lambda x: urllib.quote(x.replace(os.path.sep, '/'))
18 unquote = lambda x: os.path.normpath(urllib.unquote_plus(x))
20 class Error:
21 CONTENT_TYPE = 'text/html'
23 def GetPlugin(name):
24 try:
25 module_name = '.'.join(['plugins', name, name])
26 module = __import__(module_name, globals(), locals(), name)
27 plugin = getattr(module, module.CLASS_NAME)()
28 return plugin
29 except ImportError:
30 print 'Error no', name, 'plugin exists. Check the type ' \
31 'setting for your share.'
32 return Error
34 class EncodeUnicode(Filter):
35 def filter(self, val, **kw):
36 """Encode Unicode strings, by default in UTF-8"""
38 encoding = kw.get('encoding', 'utf8')
40 if type(val) == str:
41 try:
42 val = val.decode('utf8')
43 except:
44 if sys.platform == 'darwin':
45 val = val.decode('macroman')
46 else:
47 val = val.decode('cp1252')
48 elif type(val) != unicode:
49 val = str(val)
50 return val.encode(encoding)
52 class Plugin(object):
54 random_lock = threading.Lock()
56 CONTENT_TYPE = ''
58 recurse_cache = LRUCache(5)
59 dir_cache = LRUCache(10)
61 def __new__(cls, *args, **kwds):
62 it = cls.__dict__.get('__it__')
63 if it is not None:
64 return it
65 cls.__it__ = it = object.__new__(cls)
66 it.init(*args, **kwds)
67 return it
69 def init(self):
70 pass
72 def send_file(self, handler, path, query):
73 handler.send_content_file(unicode(path, 'utf-8'))
75 def get_local_base_path(self, handler, query):
76 return os.path.normpath(handler.container['path'])
78 def get_local_path(self, handler, query):
80 subcname = query['Container'][0]
82 path = self.get_local_base_path(handler, query)
83 for folder in subcname.split('/')[1:]:
84 if folder == '..':
85 return False
86 path = os.path.join(path, folder)
87 return path
89 def item_count(self, handler, query, cname, files, last_start=0):
90 """Return only the desired portion of the list, as specified by
91 ItemCount, AnchorItem and AnchorOffset. 'files' is either a
92 list of strings, OR a list of objects with a 'name' attribute.
93 """
94 def no_anchor(handler, anchor):
95 handler.server.logger.warning('Anchor not found: ' + anchor)
97 totalFiles = len(files)
98 index = 0
100 if totalFiles and 'ItemCount' in query:
101 count = int(query['ItemCount'][0])
103 if 'AnchorItem' in query:
104 bs = '/TiVoConnect?Command=QueryContainer&Container='
105 local_base_path = self.get_local_base_path(handler, query)
107 anchor = query['AnchorItem'][0]
108 if anchor.startswith(bs):
109 anchor = anchor.replace(bs, '/', 1)
110 anchor = unquote(anchor)
111 anchor = anchor.replace(os.path.sep + cname, local_base_path, 1)
112 if not '://' in anchor:
113 anchor = os.path.normpath(anchor)
115 if type(files[0]) == str:
116 filenames = files
117 else:
118 filenames = [x.name for x in files]
119 try:
120 index = filenames.index(anchor, last_start)
121 except ValueError:
122 if last_start:
123 try:
124 index = filenames.index(anchor, 0, last_start)
125 except ValueError:
126 no_anchor(handler, anchor)
127 else:
128 no_anchor(handler, anchor) # just use index = 0
130 if count > 0:
131 index += 1
133 if 'AnchorOffset' in query:
134 index += int(query['AnchorOffset'][0])
136 if count < 0:
137 index = (index + count) % len(files)
138 count = -count
139 files = files[index:index + count]
141 return files, totalFiles, index
143 def get_files(self, handler, query, filterFunction=None,
144 force_alpha=False, allow_recurse=True):
146 class FileData:
147 def __init__(self, name, isdir):
148 self.name = name
149 self.isdir = isdir
150 st = os.stat(unicode(name, 'utf-8'))
151 self.mdate = st.st_mtime
152 self.size = st.st_size
154 class SortList:
155 def __init__(self, files):
156 self.files = files
157 self.unsorted = True
158 self.sortby = None
159 self.last_start = 0
161 def build_recursive_list(path, recurse=True):
162 files = []
163 path = unicode(path, 'utf-8')
164 try:
165 for f in os.listdir(path):
166 if f.startswith('.'):
167 continue
168 f = os.path.join(path, f)
169 isdir = os.path.isdir(f)
170 if sys.platform == 'darwin':
171 f = unicodedata.normalize('NFC', f)
172 f = f.encode('utf-8')
173 if recurse and isdir:
174 files.extend(build_recursive_list(f))
175 else:
176 if not filterFunction or filterFunction(f, file_type):
177 files.append(FileData(f, isdir))
178 except:
179 pass
180 return files
182 subcname = query['Container'][0]
183 path = self.get_local_path(handler, query)
185 file_type = query.get('Filter', [''])[0]
187 recurse = allow_recurse and query.get('Recurse', ['No'])[0] == 'Yes'
189 filelist = []
190 rc = self.recurse_cache
191 dc = self.dir_cache
192 if recurse:
193 if path in rc and rc.mtime(path) + 300 >= time.time():
194 filelist = rc[path]
195 else:
196 updated = os.path.getmtime(unicode(path, 'utf-8'))
197 if path in dc and dc.mtime(path) >= updated:
198 filelist = dc[path]
199 for p in rc:
200 if path.startswith(p) and rc.mtime(p) < updated:
201 del rc[p]
203 if not filelist:
204 filelist = SortList(build_recursive_list(path, recurse))
206 if recurse:
207 rc[path] = filelist
208 else:
209 dc[path] = filelist
211 def dir_sort(x, y):
212 if x.isdir == y.isdir:
213 return name_sort(x, y)
214 else:
215 return y.isdir - x.isdir
217 def name_sort(x, y):
218 return cmp(x.name, y.name)
220 def date_sort(x, y):
221 return cmp(y.mdate, x.mdate)
223 sortby = query.get('SortOrder', ['Normal'])[0]
224 if filelist.unsorted or filelist.sortby != sortby:
225 if force_alpha:
226 filelist.files.sort(dir_sort)
227 elif sortby == '!CaptureDate':
228 filelist.files.sort(date_sort)
229 else:
230 filelist.files.sort(name_sort)
232 filelist.sortby = sortby
233 filelist.unsorted = False
235 files = filelist.files[:]
237 # Trim the list
238 files, total, start = self.item_count(handler, query, handler.cname,
239 files, filelist.last_start)
240 if len(files) > 1:
241 filelist.last_start = start
242 return files, total, start