Allow threads to run while processing.
[lms] / python-lightmediascanner / lightmediascanner / lightmediascanner.c_lightmediascanner.pyx
1 cdef class LightMediaScanner:
2     def __init__(self, char *db_path, parsers=None, charsets=None,
3                  slave_timeout=None, commit_interval=None):
4         if self.obj == NULL:
5             self.parsers = ()
6             self.obj = lms_new(db_path)
7             self.db_path = db_path
8             if self.obj == NULL:
9                 raise SystemError("Could not create LightMediaScanner.")
10             if parsers:
11                 for p in parsers:
12                     self.parser_find_and_add(p)
13             if charsets:
14                 for c in charsets:
15                     self.charset_add(c)
16             if slave_timeout is not None:
17                 self.set_slave_timeout(slave_timeout)
18             if commit_interval is not None:
19                 self.set_commit_interval(commit_interval)
20
21     def __dealloc__(self):
22         if self.obj != NULL:
23             if lms_free(self.obj) != 0:
24                 raise SystemError("Could not free internal object.")
25
26     def __str__(self):
27         parsers = []
28         for p in self.parsers:
29             parsers.append(str(p))
30         parsers = ", ".join(parsers)
31         return ("%s(db_path=%r, slave_timeout=%d, commit_interval=%d, "
32                 "parsers=[%s])") % (self.__class__.__name__, self.db_path,
33                                     self.slave_timeout, self.commit_interval,
34                                     parsers)
35
36     def __repr__(self):
37         parsers = []
38         for p in self.parsers:
39             parsers.append(repr(p))
40         parsers = ", ".join(parsers)
41         return ("%s(%#x, lms_t=%#x, db_path=%r, slave_timeout=%d, "
42                 "commit_interval=%d, parsers=[%s])") % \
43                 (self.__class__.__name__, <unsigned>self, <unsigned>self.obj,
44                  self.db_path, self.slave_timeout, self.commit_interval,
45                  parsers)
46
47     def process(self, char *top_path):
48         """Process directory recursively.
49
50         This operates on all files in all sub directories of top_path using
51         the added parsers.
52         """
53         if self.obj == NULL:
54             raise ValueError("LightMediaScanner is shallow.")
55         Py_BEGIN_ALLOW_THREADS
56         r = lms_process(self.obj, top_path)
57         Py_END_ALLOW_THREADS
58         return r
59
60     def check(self, char *top_path):
61         """Check (and update) files under directory.
62
63         This operates on all files in all sub directories of top_path using
64         the added parsers. If files are up to date, nothing is done, otherwise
65         they can be marked as deleted or updated if they still exists, but
66         with different size or modification time.
67         """
68         if self.obj == NULL:
69             raise ValueError("LightMediaScanner is shallow.")
70         Py_BEGIN_ALLOW_THREADS
71         r = lms_check(self.obj, top_path)
72         Py_END_ALLOW_THREADS
73         return r
74
75     def parser_find_and_add(self, char *name):
76         """Add a new plugin/parser based on it's name.
77
78         @rtype: L{Parser}
79         """
80         cdef lms_plugin_t *p
81         cdef Parser parser
82         if self.obj == NULL:
83             raise ValueError("LightMediaScanner is shallow.")
84         p = lms_parser_find_and_add(self.obj, name)
85         if p == NULL:
86             raise ValueError("LightMediaScanner cannot add parser %r" % name)
87         parser = Parser(self)
88         parser._set_obj(p)
89         self.parsers = self.parsers + (parser,)
90
91     def parser_add(self, char *so_path):
92         """Add a new plugin/parser based on it's whole path to shared object.
93
94         @rtype: L{Parser}
95         """
96         cdef lms_plugin_t *p
97         cdef Parser parser
98         if self.obj == NULL:
99             raise ValueError("LightMediaScanner is shallow.")
100         p = lms_parser_add(self.obj, so_path)
101         if p == NULL:
102             raise ValueError("LightMediaScanner cannot add parser %r" % so_path)
103         parser = Parser(self)
104         parser._set_obj(p)
105         self.parsers = self.parsers + (parser,)
106
107     def parser_del(self, Parser parser):
108         "Delete a plugin/parser."
109         if self.obj == NULL:
110             raise ValueError("LightMediaScanner is shallow.")
111         if lms_parser_del(self.obj, parser.obj) != 0:
112             raise SystemError("Could not delete parser %s" % parser)
113
114         parsers = []
115         for p in self.parsers:
116             if p != parser:
117                 parsers.append(p)
118         self.parsers = tuple(parsers)
119         parser._unset_obj()
120
121     def is_processing(self):
122         "@rtype: bool"
123         if self.obj == NULL:
124             raise ValueError("LightMediaScanner is shallow.")
125         return bool(lms_is_processing(self.obj))
126
127     def stop_processing(self):
128         "Stop process/check"
129         if self.obj == NULL:
130             raise ValueError("LightMediaScanner is shallow.")
131         lms_stop_processing(self.obj)
132
133     def get_slave_timeout(self):
134         "@rtype: int"
135         if self.obj == NULL:
136             raise ValueError("LightMediaScanner is shallow.")
137         return lms_get_slave_timeout(self.obj)
138
139     def set_slave_timeout(self, int ms):
140         """Set maximum time a parser may use.
141
142         This will be the timeout before killing the slave process running
143         some parser. If this happens, another slave process will be
144         started to continue from next file.
145         """
146         if self.obj == NULL:
147             raise ValueError("LightMediaScanner is shallow.")
148         lms_set_slave_timeout(self.obj, ms)
149
150     property slave_timeout:
151         def __get__(self):
152             return self.get_slave_timeout()
153
154         def __set__(self, int ms):
155             self.set_slave_timeout(ms)
156
157     def get_commit_interval(self):
158         "@rtype: int"
159         if self.obj == NULL:
160             raise ValueError("LightMediaScanner is shallow.")
161         return lms_get_commit_interval(self.obj)
162
163     def set_commit_interval(self, unsigned int transactions):
164         """Set the number of transactions between commits.
165
166         Sets how many transactions/files to handle in one commit, the more
167         the faster, but if one parser takes too long and it's killed due
168         slave_timeout being exceeded, then at most this number of transactions
169         will be lost.
170
171         Note that transaction here is not a single SQL statement, but it is
172         considered to be the processing of a file, which can be more than
173         just one.
174         """
175         if self.obj == NULL:
176             raise ValueError("LightMediaScanner is shallow.")
177         lms_set_commit_interval(self.obj, transactions)
178
179     property commit_interval:
180         def __get__(self):
181             return self.get_commit_interval()
182
183         def __set__(self, unsigned int transactions):
184             self.set_commit_interval(transactions)
185
186     def charset_add(self, char *charset):
187         """Add charset to list of supported input charsets/encoding.
188
189         If some string in analysed/parsed files are not UTF-8, then
190         it will try agains a list of charsets registered with this function.
191         """
192         if self.obj == NULL:
193             raise ValueError("LightMediaScanner is shallow.")
194         if lms_charset_add(self.obj, charset) != 0:
195             raise SystemError("LightMediaScanner cannot add charset %r" %
196                               charset)
197
198     def charset_del(self, char *charset):
199         "Del charset from list of supported input charsets/encoding."
200         if self.obj == NULL:
201             raise ValueError("LightMediaScanner is shallow.")
202         if lms_charset_del(self.obj, charset) != 0:
203             raise SystemError("LightMediaScanner cannot del charset %r" %
204                               charset)
205
206
207 cdef class Parser:
208     def __init__(self, scanner):
209         self.scanner = scanner
210
211     def __str__(self):
212         return "%s(name=%r)" % (self.__class__.__name__, self.name)
213
214     def __repr__(self):
215         return "%s(%#x, lms_plugin_t=%#x, name=%r)" % \
216                (self.__class__.__name__, <unsigned>self, <unsigned>self.obj,
217                 self.name)
218
219     cdef int _set_obj(self, lms_plugin_t *obj) except 0:
220         if self.obj != NULL:
221             raise ValueError("Parser already wraps an object.")
222         self.obj = obj
223         return 1
224
225     cdef int _unset_obj(self) except 0:
226         self.obj = NULL
227         return 1
228
229     def delete(self):
230         "Same as LightMediaScanner.parser_del(self)."
231         self.scanner.parser_del(self)
232
233     property name:
234         def __get__(self):
235             if self.obj == NULL:
236                 return None
237             if self.obj.name == NULL:
238                 return None
239             return self.obj.name