josh-gmail-backup 0.104

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,580 @@
1
+ # Copyright (C) 2008 Jan Svec and Filip Jurcicek
2
+ #
3
+ # YOU USE THIS TOOL ON YOUR OWN RISK!
4
+ #
5
+ # email: info@gmail-backup.com
6
+ #
7
+ #
8
+ # Disclaimer of Warranty
9
+ # ----------------------
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, licensor provides
12
+ # this tool (and each contributor provides its contributions) on an "AS IS"
13
+ # BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14
+ # implied, including, without limitation, any warranties or conditions of
15
+ # TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
16
+ # PURPOSE. You are solely responsible for determining the appropriateness of
17
+ # using this work and assume any risks associated with your exercise of
18
+ # permissions under this license.
19
+
20
+ import sys
21
+ import os
22
+ import re
23
+ import time
24
+ import getpass
25
+ from copy import copy
26
+ from threading import Thread
27
+ from select import select
28
+ from subprocess import Popen, PIPE
29
+
30
+ from svc.egg import PythonEgg, MetaEgg
31
+ from svc.scripting import *
32
+ from svc.utils import sym, issequence
33
+
34
+ import warnings
35
+
36
+ Generator = sym('Generator')
37
+ AllowOverwrite = sym('AllowOverwrite')
38
+ OmitStdout = sym('OmitStdout')
39
+ AsyncMethod = sym('AsyncMethod')
40
+ # TODO:
41
+ # GNUargs = sym('GNUargs')
42
+ ExecGenerator = sym('ExecGenerator')
43
+ ExecList = sym('ExecList')
44
+ ExecStr = sym('ExecStr')
45
+ ExecNoStdout = sym('ExecNoStdout')
46
+ ExecAsync = sym('ExecAsync')
47
+
48
+ class ExternalError(OSError):
49
+ pass
50
+
51
+ class ExternalMethod(PythonEgg):
52
+ def __init__(self, file_name, name=None, args=[], logger=None, env=None, path=None, etype=ExecGenerator, pre_func=None, post_func=None):
53
+ super(ExternalMethod, self).__init__()
54
+ if path is None:
55
+ self.fileName = file_name
56
+ else:
57
+ self.fileName = os.path.join(path, file_name)
58
+ if name is None:
59
+ self.name = os.path.basename(file_name)
60
+ else:
61
+ self.name = name
62
+ self.args = args
63
+ self.logger = logger
64
+ self.env = env
65
+ if self.getExecuteMethod(etype) is None:
66
+ raise ValueError("Unknown execute type: %r" % etype)
67
+ self.etype = etype
68
+ self.pre_func = pre_func
69
+ self.post_func = post_func
70
+
71
+ def _logStderr(self, line):
72
+ if self.logger:
73
+ self.logger(self, line)
74
+ else:
75
+ sys.stderr.write(line)
76
+
77
+ def getOSEnv(self):
78
+ return dict(os.environ)
79
+
80
+ def getEnv(self):
81
+ e = self.getOSEnv()
82
+ if self._env is not None:
83
+ e.update(self._env)
84
+ return e
85
+
86
+ def setEnv(self, env):
87
+ self._env = env
88
+
89
+ def _strEnv(self, env):
90
+ ret = {}
91
+ for key, value in env.iteritems():
92
+ ret[key] = str(value)
93
+ return ret
94
+
95
+ def _strArgs(self, args):
96
+ return [str(a) for a in args]
97
+
98
+ def getArgs(self):
99
+ return self._args
100
+
101
+ def setArgs(self, args):
102
+ self._args = args
103
+
104
+ def execute(self, *args, **kwargs):
105
+ """Executes program `fn` with cmdline `args` and in environment `env`
106
+
107
+ It would raise an ExternalError if the program `fn` returned non-zero
108
+ exit status. Environment of new process is initialized using
109
+ `self.getEnv` method and updated with `env`.
110
+
111
+ :Returns:
112
+ Generator yielding lines of stdout of `fn`
113
+ """
114
+ stdin = kwargs.pop('stdin', None)
115
+ stdin_file = kwargs.pop('stdin_file', None)
116
+ stdout_file = kwargs.pop('stdout_file', None)
117
+ env = kwargs.pop('env', {})
118
+
119
+ if kwargs:
120
+ raise TypeError("Bad keyword arguments: %s" % kwargs.keys())
121
+
122
+ if stdin is not None and stdin_file is not None:
123
+ raise TypeError("You cannot use both stdin and stdin_file argument")
124
+
125
+ e = dict(self.getEnv())
126
+ e.update(env)
127
+ e = self._strEnv(e)
128
+ args = self._strArgs(self.args + list(args))
129
+
130
+ to_close = []
131
+
132
+ if stdin is not None:
133
+ stdin_func = iter(stdin)
134
+ stdin = PIPE
135
+ elif stdin_file is not None:
136
+ stdin_func = None
137
+ stdin = file(stdin_file, 'r')
138
+ to_close.append(stdin)
139
+ else:
140
+ stdin_func = None
141
+ stdin = None
142
+
143
+ if stdout_file is not None:
144
+ stdout = file(stdout_file, 'w')
145
+ to_close.append(stdout)
146
+ else:
147
+ stdout = PIPE
148
+
149
+ exec_list = [self.fileName] + args
150
+ if self.pre_func is not None:
151
+ self.pre_func(self, exec_list, env)
152
+ try:
153
+ process = Popen(exec_list, shell=False, env=e,
154
+ stdin=stdin, stdout=stdout, stderr=PIPE, bufsize=0)
155
+ except OSError, e:
156
+ raise ExternalError("Couldn't execute external method %r: %s" % (self.name, e))
157
+
158
+ stdin = process.stdin
159
+ stdout = process.stdout
160
+ stderr = process.stderr
161
+ if stdout_file is not None:
162
+ poll_r = [stderr]
163
+ else:
164
+ poll_r = [stdout, stderr]
165
+ if stdin_func is not None:
166
+ poll_w = [stdin]
167
+ else:
168
+ poll_w = []
169
+
170
+ while poll_r or poll_w:
171
+ readable, writeable, errorable = select(poll_r, poll_w, [])
172
+
173
+ if stderr in readable:
174
+ line = stderr.readline()
175
+ if not line:
176
+ stderr.close()
177
+ poll_r.remove(stderr)
178
+ else:
179
+ self._logStderr(line)
180
+ continue
181
+
182
+ if stdout in readable:
183
+ line = stdout.readline()
184
+ if not line:
185
+ stdout.close()
186
+ poll_r.remove(stdout)
187
+ else:
188
+ yield line
189
+
190
+ if stdin in errorable:
191
+ poll_w.remove(stdin)
192
+ elif stdin in writeable:
193
+ try:
194
+ stdin.write(stdin_func.next())
195
+ except StopIteration:
196
+ stdin.close()
197
+ poll_w.remove(stdin)
198
+
199
+ retcode = process.wait()
200
+ for f in to_close:
201
+ f.close()
202
+ if self.post_func is not None:
203
+ self.post_func(self, exec_list, env)
204
+ if retcode < 0:
205
+ raise ExternalError("External method %s (%r) killed by signal (%d)" % (self.name, ' '.join(exec_list), -retcode))
206
+ elif retcode > 0:
207
+ raise ExternalError("External method %s (%r) returned with nonzero exit status (%d)" % (self.name, ' '.join(exec_list), retcode))
208
+
209
+ def executeGenerator(self, *args, **kwargs):
210
+ return self.execute(*args, **kwargs)
211
+
212
+ __iter__ = executeGenerator
213
+
214
+ def executeList(self, *args, **kwargs):
215
+ return list(self.execute(*args, **kwargs))
216
+
217
+ def executeStr(self, *args, **kwargs):
218
+ return ''.join(self.execute(*args, **kwargs))
219
+
220
+ def executeNoStdout(self, *args, **kwargs):
221
+ for foo in self.execute(*args, **kwargs):
222
+ pass
223
+
224
+ def executeAsync(self, *args, **kwargs):
225
+ t = Thread(target=self.executeNoStdout, args=args, kwargs=kwargs)
226
+ t.setDaemon(False)
227
+ t.start()
228
+ return t
229
+
230
+ def getExecuteMethod(self, etype):
231
+ etype = str(etype)
232
+ if not etype.startswith('Exec'):
233
+ return None
234
+ etype = etype[4:]
235
+ return getattr(self, 'execute%s' % etype, None)
236
+
237
+ def __call__(self, *args, **kwargs):
238
+ m = self.getExecuteMethod(self.etype)
239
+ return m(*args, **kwargs)
240
+
241
+ def new(self, *args, **kwargs):
242
+ if 'stdin' in kwargs:
243
+ stdin = kwargs.pop('stdin')
244
+ else:
245
+ stdin = None
246
+
247
+ if 'env' in kwargs:
248
+ env = kwargs.pop('env')
249
+ else:
250
+ env = {}
251
+
252
+ if kwargs:
253
+ raise TypeError("Bad keyword arguments: %s" % kwargs.keys())
254
+
255
+ e = dict(self.getEnv())
256
+ e.update(env)
257
+ e = self._strEnv(e)
258
+ args = self._strArgs(self.args + list(args))
259
+
260
+ ret = copy(self)
261
+ ret.setArgs(args)
262
+ return ret
263
+
264
+
265
+ # Pipeline overloading
266
+ def __or__(left, right):
267
+ return Pipeline( (left, right) )
268
+
269
+ def __ror__(right, left):
270
+ return Pipeline( (left, right) )
271
+
272
+ def __rshift__(self, fn):
273
+ return Pipeline( (self,), redir_stdout=fn)
274
+
275
+ def __rrshift__(self, fn):
276
+ return Pipeline( (self,), redir_stdin=fn)
277
+
278
+ class Pipeline(PythonEgg):
279
+ def __init__(self, pipeline, redir_stdin=None, redir_stdout=None):
280
+ super(Pipeline, self).__init__()
281
+ self.setupPipeline(pipeline, redir_stdin, redir_stdout)
282
+
283
+ def setupPipeline(self, pipeline, redir_stdin, redir_stdout):
284
+ ret = []
285
+
286
+ no_stdin = False
287
+
288
+ for i, item in enumerate(pipeline):
289
+ first = (i==0)
290
+ last = (i==len(pipeline)-1)
291
+
292
+ item_callable = callable(item)
293
+
294
+ if not item_callable:
295
+ if first:
296
+ no_stdin = True
297
+ else:
298
+ raise ValueError("Items in pipeline must be callable")
299
+
300
+ if isinstance(item, Pipeline):
301
+ if first and item.redir_stdin is not None:
302
+ if redir_stdin:
303
+ raise ValueError("Input of pipeline is redirected")
304
+ else:
305
+ redir_stdin = item.redir_stdin
306
+ if last and item.redir_stdout is not None:
307
+ if redir_stdout:
308
+ raise ValueError("Output of pipeline is redirected")
309
+ else:
310
+ redir_stdout = item.redir_stdout
311
+ ret.extend(item._pipeline)
312
+ else:
313
+ if not callable(item):
314
+ if first:
315
+ no_stdin = True
316
+ else:
317
+ raise ValueError("Items in pipeline must be callable")
318
+ ret.append(item)
319
+
320
+ self._pipeline = tuple(ret)
321
+ self.no_stdin = no_stdin
322
+ self.redir_stdin = redir_stdin
323
+ self.redir_stdout = redir_stdout
324
+
325
+ def getOSEnv(self):
326
+ return dict(os.environ)
327
+
328
+ def _strEnv(self, env):
329
+ ret = {}
330
+ for key, value in env.iteritems():
331
+ ret[key] = str(value)
332
+ return ret
333
+
334
+ def execute(self, stdin=None, env={}):
335
+ e = self.getOSEnv()
336
+ e.update(env)
337
+ e = self._strEnv(e)
338
+
339
+ pipeline = self._pipeline
340
+
341
+ if self.no_stdin and stdin is not None:
342
+ raise ValueError("Pipeline doesn't have a stdin")
343
+
344
+ if self.redir_stdin and stdin is not None:
345
+ raise ValueError("Pipeline have a redirected stdin")
346
+
347
+ generator = stdin
348
+ for i, item in enumerate(pipeline):
349
+ first = (i==0)
350
+ last = (i==len(pipeline)-1)
351
+
352
+ if isinstance(item, ExternalMethod):
353
+ if not last:
354
+ method = item.executeGenerator
355
+ else:
356
+ method = item
357
+ kwargs = {}
358
+ if self.redir_stdin is not None and first:
359
+ kwargs['stdin_file'] = self.redir_stdin
360
+ else:
361
+ kwargs['stdin'] = generator
362
+ if self.redir_stdout is not None and last:
363
+ kwargs['stdout_file'] = self.redir_stdout
364
+ generator = method(env=e, **kwargs)
365
+ elif callable(item):
366
+ generator = item(generator)
367
+ else:
368
+ generator = item
369
+ return generator
370
+
371
+ def __call__(self, *args, **kwargs):
372
+ return self.execute(*args, **kwargs)
373
+
374
+ def __iter__(self):
375
+ return self.execute()
376
+
377
+ # Pipeline overloading
378
+ def __or__(left, right):
379
+ return Pipeline( (left, right) )
380
+
381
+ def __ror__(right, left):
382
+ return Pipeline( (left, right) )
383
+
384
+ def __rshift__(self, fn):
385
+ return Pipeline( (self,), redir_stdout=fn)
386
+
387
+ def __rrshift__(self, fn):
388
+ return Pipeline( (self,), redir_stdin=fn)
389
+
390
+ class MetaExternalMethods(MetaEgg):
391
+ def __init__(cls, name, bases, dict):
392
+ super(MetaExternalMethods, cls).__init__(name, bases, dict)
393
+ if cls.externalMethods:
394
+ cls.__bases__ = cls.__bases__ + (cls.createClassExternals(), )
395
+
396
+ def createClassExternals(cls):
397
+ class rClass(object):
398
+ pass
399
+
400
+ rClass.__name__ = '%sExternals' % cls.__name__
401
+ rClass.__module__ = cls.__module__
402
+
403
+ meth_dict = cls.unifyExternalMethods(cls.externalMethods)
404
+ done_dict = {}
405
+
406
+ for dir in cls.externalMethodDirs:
407
+ for meth_name in os.listdir(dir):
408
+ meth_path = os.path.join(dir, meth_name)
409
+ if meth_name in meth_dict:
410
+ # TODO: Check for inconsitencies in method specifiers
411
+ if Generator in meth_dict[meth_name]:
412
+ warnings.warn("Don't use Generator, use ExecGenerator instead", DeprecationWarning, 3)
413
+ meth = cls.createMethod(meth_name, meth_path, ExecGenerator)
414
+ elif OmitStdout in meth_dict[meth_name]:
415
+ warnings.warn("Don't use OmitStdout, use ExecNoStdout instead", DeprecationWarning, 3)
416
+ meth = cls.createMethod(meth_name, meth_path, ExecNoStdout)
417
+ elif AsyncMethod in meth_dict[meth_name]:
418
+ warnings.warn("Don't use AsyncMethod, use ExecAsync instead", DeprecationWarning, 3)
419
+ meth = cls.createMethod(meth_name, meth_path, ExecAsync)
420
+ elif ExecGenerator in meth_dict[meth_name]:
421
+ meth = cls.createMethod(meth_name, meth_path, ExecGenerator)
422
+ elif ExecNoStdout in meth_dict[meth_name]:
423
+ meth = cls.createMethod(meth_name, meth_path, ExecNoStdout)
424
+ elif ExecAsync in meth_dict[meth_name]:
425
+ meth = cls.createMethod(meth_name, meth_path, ExecAsync)
426
+ else:
427
+ meth = cls.createMethod(meth_name, meth_path, ExecList)
428
+
429
+ if ExScript.command in meth_dict[meth_name]:
430
+ meth = ExScript.command(meth)
431
+ child_meth = getattr(cls, meth_name, None)
432
+ if child_meth is not None and not ExScript.isCommand(child_meth):
433
+ raise TypeError("External method %r defined as ExScript.command, but method in derived class %r is not decorated" % \
434
+ (meth_name, cls.__name__))
435
+
436
+ if meth_name in done_dict and AllowOverwrite not in meth_dict[meth_name]:
437
+ raise TypeError("External method %r is defined in two different places: %r and %r" % \
438
+ (meth_name, done_dict[meth_name], dir))
439
+ setattr(rClass, meth_name, meth)
440
+
441
+ done_dict[meth_name] = dir
442
+
443
+ rest = set(meth_dict) - set(done_dict)
444
+ if rest:
445
+ raise ValueError('External method %r not found' % rest.pop())
446
+
447
+ return rClass
448
+
449
+ def unifyExternalMethods(cls, em):
450
+ ret = {}
451
+ for meth_name, specifiers in em.iteritems():
452
+ if issequence(specifiers):
453
+ ret[meth_name] = set(specifiers)
454
+ else:
455
+ ret[meth_name] = set([specifiers])
456
+ return ret
457
+
458
+ def createMethod(cls, name, path, etype):
459
+ def rMethod(self, *args, **kwargs):
460
+ meth = ExternalMethod(path, name=name, etype=etype,
461
+ logger=self._logExternalStderr, env=self._externalEnv,
462
+ pre_func=self._logPreExec, post_func=self._logPostExec)
463
+ return meth(*args, **kwargs)
464
+
465
+ cls.adoptMethod(rMethod, name)
466
+ return rMethod
467
+
468
+ def adoptMethod(cls, meth, name):
469
+ meth.__name__ = name
470
+ meth.__module__ = cls.__module__
471
+
472
+
473
+ class ExternalMethods(PythonEgg):
474
+ __metaclass__ = MetaExternalMethods
475
+ externalMethodDirs = []
476
+ externalMethods = {}
477
+
478
+ def _logPreExec(self, method, cmdline, env):
479
+ pass
480
+
481
+ def _logPostExec(self, method, cmdline, env):
482
+ pass
483
+
484
+ def _logExternalStderr(self, method, line):
485
+ sys.stdout.write(line)
486
+
487
+ def _getExternalEnv(self):
488
+ return os.environ
489
+
490
+ class ExternalScript(ExScript, ExternalMethods):
491
+ externalMethodDirs = []
492
+ externalMethods = {}
493
+ settingsFiles = []
494
+
495
+ def __init__(self, *args, **kwargs):
496
+ super(ExternalScript, self).__init__(*args, **kwargs)
497
+ self._settings = dict(os.environ)
498
+
499
+ def getSettings(self):
500
+ return self._settings
501
+
502
+ def sourceEnv(self, fn):
503
+ SKIP = ['PWD', 'SHLVL', '_']
504
+ env = Popen(['/bin/bash', '-c', 'source %s; env' % fn], env=self.settings, stdout=PIPE).communicate()[0]
505
+ for line in env.splitlines():
506
+ try:
507
+ name, val = line.split('=', 1)
508
+ except ValueError:
509
+ continue
510
+ if val.startswith('()'):
511
+ continue
512
+ if name in SKIP: continue
513
+ self.settings[name] = val
514
+
515
+ def storeEnv(self, fn, info={}):
516
+ AU = 'Author'
517
+ DT = 'Date'
518
+ FN = 'Filename'
519
+ info = dict(info)
520
+
521
+ info[AU] = getpass.getuser()
522
+ info[DT] = time.strftime('%Y-%m-%d-%H:%M:%S')
523
+ info[FN] = os.path.basename(fn)
524
+ key_order = [AU, DT, FN]
525
+
526
+ fw = file(fn, 'w')
527
+ try:
528
+ fw.write("#!/bin/bash\n\n")
529
+ while info:
530
+ if key_order:
531
+ key = key_order.pop(0)
532
+ else:
533
+ key = sorted(info)[0]
534
+ value = info.pop(key)
535
+ fw.write("# %20s: %s\n" % (key, value))
536
+ fw.write('\n')
537
+
538
+ for key, value in sorted(self.settings.items()):
539
+ value = str(value)
540
+ if key not in os.environ or value != os.environ[key]:
541
+ if re.search(r'\s', value):
542
+ fw.write('export %s="%s"\n' % (key, value))
543
+ else:
544
+ fw.write('export %s=%s\n' % (key, value))
545
+
546
+ fw.write("\n\n###########################\n# Operating environment #\n###########################\n\n")
547
+ for key, value in sorted(os.environ.items()):
548
+ if re.search(r'\s', value):
549
+ fw.write('# %s="%s"\n' % (key, value))
550
+ else:
551
+ fw.write('# %s=%s\n' % (key, value))
552
+ finally:
553
+ fw.close()
554
+
555
+ def _getExternalEnv(self):
556
+ return self.settings
557
+
558
+ def _logPreExec(self, method, cmdline, env):
559
+ self.logger.info('Running external method %s.%s (%r)', self.__class__.__name__, method.name, ' '.join(cmdline))
560
+
561
+ def _logExternalStderr(self, method, line):
562
+ for l in line.splitlines():
563
+ self.logger.error('%s.%s: %s', self.__class__.__name__, method.name, l)
564
+
565
+ def getManagerArgs(self):
566
+ m_args = super(ExternalScript, self).getManagerArgs()
567
+ m_args['specification'].update({
568
+ '__premain__.settings': (Multiple, String),
569
+ })
570
+ m_args['docs'].update({
571
+ 'settings': "Additional shell files with settings",
572
+ })
573
+ return m_args
574
+
575
+ def premain(self, settings=[], **kwargs):
576
+ ret = super(ExternalScript, self).premain(**kwargs)
577
+ settings = self.settingsFiles + settings
578
+ for fn in settings:
579
+ self.sourceEnv(fn)
580
+ return ret