1 |
commit: 9a0b0c9e1740e88ee2426fefa2c97fb7e629bdb1 |
2 |
Author: André Erdmann <dywi <AT> mailerd <DOT> de> |
3 |
AuthorDate: Mon Jun 25 18:15:38 2012 +0000 |
4 |
Commit: André Erdmann <dywi <AT> mailerd <DOT> de> |
5 |
CommitDate: Mon Jun 25 18:15:38 2012 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=9a0b0c9e |
7 |
|
8 |
the remote module (used for repo syncing) |
9 |
|
10 |
* this modules handles repositories, both local (directory) |
11 |
and remote (currently only rsync) |
12 |
|
13 |
* some parts, mainly integration into roverlay, are todo |
14 |
|
15 |
new file: roverlay/remote/__init__.py |
16 |
new file: roverlay/remote/basicrepo.py |
17 |
new file: roverlay/remote/repo.py |
18 |
new file: roverlay/remote/repolist.py |
19 |
new file: roverlay/remote/repoloader.py |
20 |
new file: roverlay/remote/rsync.py |
21 |
|
22 |
--- |
23 |
roverlay/remote/__init__.py | 1 + |
24 |
roverlay/remote/basicrepo.py | 245 +++++++++++++++++++++++++++++++++++++++++ |
25 |
roverlay/remote/repo.py | 54 +++++++++ |
26 |
roverlay/remote/repolist.py | 55 +++++++++ |
27 |
roverlay/remote/repoloader.py | 66 +++++++++++ |
28 |
roverlay/remote/rsync.py | 86 ++++++++++++++ |
29 |
6 files changed, 507 insertions(+), 0 deletions(-) |
30 |
|
31 |
diff --git a/roverlay/remote/__init__.py b/roverlay/remote/__init__.py |
32 |
new file mode 100644 |
33 |
index 0000000..e7521be |
34 |
--- /dev/null |
35 |
+++ b/roverlay/remote/__init__.py |
36 |
@@ -0,0 +1 @@ |
37 |
+from roverlay.remote.repolist import RepoList |
38 |
|
39 |
diff --git a/roverlay/remote/basicrepo.py b/roverlay/remote/basicrepo.py |
40 |
new file mode 100644 |
41 |
index 0000000..9ade3a2 |
42 |
--- /dev/null |
43 |
+++ b/roverlay/remote/basicrepo.py |
44 |
@@ -0,0 +1,245 @@ |
45 |
+import os.path |
46 |
+ |
47 |
+from roverlay import config |
48 |
+from roverlay.packageinfo import PackageInfo |
49 |
+ |
50 |
+URI_SEPARATOR = '://' |
51 |
+DEFAULT_PROTOCOL = 'http' |
52 |
+ |
53 |
+LOCALREPO_SRC_URI = 'http://localhost/R-Packages' |
54 |
+ |
55 |
+def normalize_uri ( uri, protocol, force_protocol=False ): |
56 |
+ |
57 |
+ if not protocol: |
58 |
+ return uri |
59 |
+ |
60 |
+ proto, sep, base_uri = uri.partition ( URI_SEPARATOR ) |
61 |
+ if sep != URI_SEPARATOR: |
62 |
+ return URI_SEPARATOR.join ( ( protocol, uri ) ) |
63 |
+ elif force_protocol: |
64 |
+ return URI_SEPARATOR.join ( ( protocol, base_uri ) ) |
65 |
+ else: |
66 |
+ return uri |
67 |
+# --- end of normalize_uri (...) --- |
68 |
+ |
69 |
+class LocalRepo ( object ): |
70 |
+ """ |
71 |
+ This class represents a local repository - all packages are assumed |
72 |
+ to exist in its distfiles dir and no remote syncing will occur. |
73 |
+ It's the base class for remote repos. |
74 |
+ """ |
75 |
+ |
76 |
+ def __init__ ( self, name, directory=None, src_uri=None ): |
77 |
+ """Initializes a LocalRepo. |
78 |
+ |
79 |
+ arguments: |
80 |
+ * name -- |
81 |
+ * directory -- distfiles dir, defaults to <DISTFILES root>/<name> |
82 |
+ * src_uri -- SRC_URI, defaults to http://localhost/R-Packages/<name> |
83 |
+ """ |
84 |
+ self.name = name |
85 |
+ if directory is None: |
86 |
+ self.distdir = os.path.join ( |
87 |
+ config.get_or_fail ( [ 'DISTFILES', 'root' ] ), |
88 |
+ # subdir repo names like CRAN/contrib are ok, |
89 |
+ # but make sure to use the correct path separator |
90 |
+ self.name.replace ( '/', os.path.sep ), |
91 |
+ ) |
92 |
+ else: |
93 |
+ self.distdir = directory |
94 |
+ |
95 |
+ if src_uri is None: |
96 |
+ self.src_uri = '/'.join ( ( LOCALREPO_SRC_URI, self.name ) ) |
97 |
+ else: |
98 |
+ self.src_uri = src_uri |
99 |
+ |
100 |
+ # --- end of __init__ (...) --- |
101 |
+ |
102 |
+ def __str__ ( self ): |
103 |
+ return "repo '%s': DISTDIR '%s', SRC_URI '%s'" % ( |
104 |
+ self.name, self.distdir, self.src_uri |
105 |
+ ) |
106 |
+ |
107 |
+ def get_name ( self ): |
108 |
+ """Returns the name of this repository.""" |
109 |
+ return self.name |
110 |
+ # --- end of get_name (...) --- |
111 |
+ |
112 |
+ def get_distdir ( self ): |
113 |
+ """Returns the distfiles directory of this repository.""" |
114 |
+ return self.distdir |
115 |
+ # --- end of get_distdir (...) --- |
116 |
+ |
117 |
+ def get_src_uri ( self, package_file=None ): |
118 |
+ """Returns the SRC_URI of this repository. |
119 |
+ |
120 |
+ arguments: |
121 |
+ * package_file -- if set and not None: returns a SRC_URI for this pkg |
122 |
+ """ |
123 |
+ if package_file is None: |
124 |
+ return self.src_uri |
125 |
+ else: |
126 |
+ return '/'.join ( self.src_uri, package_file ) |
127 |
+ # --- end of get_src_uri (...) --- |
128 |
+ |
129 |
+ # get_src(...) -> get_src_uri(...) |
130 |
+ get_src = get_src_uri |
131 |
+ |
132 |
+ def exists ( self ): |
133 |
+ """Returns True if this repo locally exists.""" |
134 |
+ return os.path.isdir ( self.distdir ) |
135 |
+ # --- end of exists (...) --- |
136 |
+ |
137 |
+ def nosync ( self ): |
138 |
+ """Returns True if the repo is ready for overlay creation, else False. |
139 |
+ Useful for basic local distfiles verification without downloading |
140 |
+ anything. |
141 |
+ """ |
142 |
+ return self.exists() |
143 |
+ |
144 |
+ # --- end of nosync (...) --- |
145 |
+ |
146 |
+ # sync() -> nosync(), LocalRepos don't have anything to sync |
147 |
+ sync = nosync |
148 |
+ |
149 |
+ def scan_distdir ( self, is_package=None ): |
150 |
+ """Generator that scans the local distfiles dir of this repo and |
151 |
+ yields PackageInfo instances. |
152 |
+ |
153 |
+ arguments: |
154 |
+ * is_package -- function returning True if the given file is a package |
155 |
+ or None which means that all files are packages. |
156 |
+ Defaults to None. |
157 |
+ """ |
158 |
+ if is_package is None: |
159 |
+ # unfiltered variant |
160 |
+ |
161 |
+ for dirpath, dirnames, filenames in os.walk ( self.distdir ): |
162 |
+ for pkg in filenames: |
163 |
+ yield PackageInfo ( filename=pkg, origin=self ) |
164 |
+ |
165 |
+ elif hasattr ( is_package, '__call__' ): |
166 |
+ # filtered variant (adds an if is_package... before yield) |
167 |
+ for dirpath, dirnames, filenames in os.walk ( self.distdir ): |
168 |
+ for pkg in filenames: |
169 |
+ if is_package ( os.path.join ( dirpath, pkg ) ): |
170 |
+ yield PackageInfo ( filename=pkg, origin=self ) |
171 |
+ |
172 |
+ |
173 |
+ else: |
174 |
+ # faulty variant, raises Exception |
175 |
+ raise Exception ( "is_package should either be None or a function." ) |
176 |
+ #yield None |
177 |
+ |
178 |
+ # --- end of scan_distdir (...) --- |
179 |
+ |
180 |
+# --- end of LocalRepo --- |
181 |
+ |
182 |
+ |
183 |
+class RemoteRepo ( LocalRepo ): |
184 |
+ """A template for remote repositories.""" |
185 |
+ |
186 |
+ def __init__ ( |
187 |
+ self, name, sync_proto, |
188 |
+ directory=None, |
189 |
+ src_uri=None, remote_uri=None, base_uri=None |
190 |
+ ): |
191 |
+ """Initializes a RemoteRepo. |
192 |
+ Mainly consists of URI calculation that derived classes may find useful. |
193 |
+ |
194 |
+ arguments: |
195 |
+ * name -- |
196 |
+ * sync_proto -- protocol used for syncing (e.g. 'rsync') |
197 |
+ * directory -- |
198 |
+ * src_uri -- src uri, if set, else calculated using base/remote uri, |
199 |
+ the leading <proto>:// can be left out in which case |
200 |
+ http is assumed |
201 |
+ * remote_uri -- uri used for syncing, if set, else calculated using |
202 |
+ base/src uri, the leading <proto>:// can be left out |
203 |
+ * base_uri -- used to calculate remote/src uri, |
204 |
+ example: localhost/R-packages/something |
205 |
+ |
206 |
+ keyword condition: |
207 |
+ * | { x : x in union(src,remote,base) and x not None } | >= 1 |
208 |
+ ^= at least one out of src/remote/base uri is not None |
209 |
+ """ |
210 |
+ super ( RemoteRepo, self ) . __init__ ( name, directory, src_uri='' ) |
211 |
+ |
212 |
+ self.sync_proto = sync_proto |
213 |
+ |
214 |
+ # detemerine uris |
215 |
+ if src_uri is None and remote_uri is None: |
216 |
+ if base_uri is None: |
217 |
+ # keyword condition not met |
218 |
+ raise Exception ( "Bad initialization of RemoteRepo!" ) |
219 |
+ |
220 |
+ else: |
221 |
+ # using base_uri for src,remote |
222 |
+ self.src_uri = URI_SEPARATOR.join ( |
223 |
+ ( DEFAULT_PROTOCOL, base_uri ) |
224 |
+ ) |
225 |
+ |
226 |
+ self.remote_uri = URI_SEPARATOR.join ( |
227 |
+ ( sync_proto, base_uri ) |
228 |
+ ) |
229 |
+ |
230 |
+ elif src_uri is None: |
231 |
+ # remote_uri is not None |
232 |
+ self.remote_uri = normalize_uri ( remote_uri, self.sync_proto ) |
233 |
+ |
234 |
+ if base_uri is not None: |
235 |
+ # using base_uri for src_uri |
236 |
+ self.src_uri = URI_SEPARATOR.join ( |
237 |
+ ( DEFAULT_PROTOCOL, base_uri ) |
238 |
+ ) |
239 |
+ else: |
240 |
+ # using remote_uri for src_uri |
241 |
+ self.src_uri = normalize_uri ( |
242 |
+ self.remote_uri, DEFAULT_PROTOCOL, force_protocol=True |
243 |
+ ) |
244 |
+ |
245 |
+ elif remote_uri is None: |
246 |
+ # src_uri is not None |
247 |
+ self.src_uri = normalize_uri ( src_uri, DEFAULT_PROTOCOL ) |
248 |
+ |
249 |
+ if base_uri is not None: |
250 |
+ # using base_uri for remote_uri |
251 |
+ self.remote_uri = URI_SEPARATOR.join ( |
252 |
+ ( self.sync_proto, base_uri ) |
253 |
+ ) |
254 |
+ else: |
255 |
+ # using src_uri for remote_uri |
256 |
+ self.remote_uri = normalize_uri ( |
257 |
+ self.src_uri, self.sync_proto, force_protocol=True |
258 |
+ ) |
259 |
+ else: |
260 |
+ # remote and src not None |
261 |
+ self.remote_uri = normalize_uri ( remote_uri, self.sync_proto ) |
262 |
+ self.src_uri = normalize_uri ( src_uri, DEFAULT_PROTOCOL ) |
263 |
+ |
264 |
+ # --- end of __init__ (...) --- |
265 |
+ |
266 |
+ def get_remote_uri ( self ): |
267 |
+ """Returns the remote uri of this RemoteRepo which used for syncing.""" |
268 |
+ return self.remote_uri |
269 |
+ # --- end of get_remote_uri (...) --- |
270 |
+ |
271 |
+ # get_remote(...) -> get_remote_uri(...) |
272 |
+ get_remote = get_remote_uri |
273 |
+ |
274 |
+ def sync ( self ): |
275 |
+ """Gets packages from remote(s) and returns True if the repo is ready |
276 |
+ for overlay creation, else False. |
277 |
+ |
278 |
+ Derived classes have to implement this method. |
279 |
+ """ |
280 |
+ raise Exception ( "RemoteRepo does not implement sync()." ) |
281 |
+ # --- end of sync (...) --- |
282 |
+ |
283 |
+ def __str__ ( self ): |
284 |
+ return "repo '%s': DISTDIR '%s', SRC_URI '%s', REMOTE_URI '%s'" % ( |
285 |
+ self.name, self.distdir, self.src_uri, self.remote_uri |
286 |
+ ) |
287 |
+ |
288 |
+# --- end of RemoteRepo --- |
289 |
+ |
290 |
|
291 |
diff --git a/roverlay/remote/repo.py b/roverlay/remote/repo.py |
292 |
new file mode 100644 |
293 |
index 0000000..f54f448 |
294 |
--- /dev/null |
295 |
+++ b/roverlay/remote/repo.py |
296 |
@@ -0,0 +1,54 @@ |
297 |
+ |
298 |
+import logging |
299 |
+ |
300 |
+#from roverlay.remote.basicrepo import LocalRepo, RemoteRepo |
301 |
+from roverlay.remote.basicrepo import RemoteRepo |
302 |
+ |
303 |
+from roverlay.remote.rsync import RsyncJob |
304 |
+ |
305 |
+class RsyncRepo ( RemoteRepo ): |
306 |
+ |
307 |
+ def __init__ ( |
308 |
+ self, name, |
309 |
+ directory=None, src_uri=None, rsync_uri=None, base_uri=None, |
310 |
+ extra_rsync_opts=None |
311 |
+ ): |
312 |
+ # super's init: name, remote protocol, directory_kw, **uri_kw |
313 |
+ # using '' as remote protocol which leaves uris unchanged when |
314 |
+ # normalizing them for rsync usage |
315 |
+ super ( RsyncRepo, self ) . __init__ ( |
316 |
+ name, '', directory=directory, |
317 |
+ src_uri=src_uri, remote_uri=rsync_uri, base_uri=base_uri |
318 |
+ ) |
319 |
+ self.extra_rsync_opts = extra_rsync_opts |
320 |
+ # --- end of __init__ (...) --- |
321 |
+ |
322 |
+ |
323 |
+ def sync ( self ): |
324 |
+ retcode = None |
325 |
+ try: |
326 |
+ job = RsyncJob ( |
327 |
+ remote=self.remote_uri, distdir=self.distdir, |
328 |
+ run_now=True, |
329 |
+ extra_opts=self.extra_rsync_opts |
330 |
+ ) |
331 |
+ if job.returncode == 0: return True |
332 |
+ |
333 |
+ retcode = job.returncode |
334 |
+ except Exception as e: |
335 |
+ # catch exceptions, log them and return False |
336 |
+ ## TODO: which exceptions to catch||pass? |
337 |
+ logging.exception ( e ) |
338 |
+ retcode = '<undef>' |
339 |
+ |
340 |
+ |
341 |
+ logging.error ( |
342 |
+ 'Repo %s cannot be used for ebuild creation due to errors ' |
343 |
+ 'while running rsync (return code was %s).' % ( self.name, retcode ) |
344 |
+ ) |
345 |
+ return False |
346 |
+ # --- end of sync (...) --- |
347 |
+ |
348 |
+ def __str__ ( self ): |
349 |
+ return "rsync repo '%s': DISTDIR '%s', SRC_URI '%s', RSYNC_URI '%s'" \ |
350 |
+ % ( self.name, self.distdir, self.src_uri, self.remote_uri ) |
351 |
|
352 |
diff --git a/roverlay/remote/repolist.py b/roverlay/remote/repolist.py |
353 |
new file mode 100644 |
354 |
index 0000000..4617057 |
355 |
--- /dev/null |
356 |
+++ b/roverlay/remote/repolist.py |
357 |
@@ -0,0 +1,55 @@ |
358 |
+ |
359 |
+from roverlay import config |
360 |
+ |
361 |
+from roverlay.remote.repoloader import read_repofile |
362 |
+ |
363 |
+class RepoList ( object ): |
364 |
+ |
365 |
+ def __init__ ( self ): |
366 |
+ self.repos = list() |
367 |
+ self.sync_enabled = True |
368 |
+ self.use_broken_repos = False |
369 |
+ |
370 |
+ def sort ( self ): |
371 |
+ raise Exception ( "method stub." ) |
372 |
+ |
373 |
+ def load_file ( self, _file ): |
374 |
+ new_repos = read_repofile ( _file ) |
375 |
+ if new_repos: |
376 |
+ self.repos.extend ( new_repos ) |
377 |
+ # --- end of load_file (...) --- |
378 |
+ |
379 |
+ def load ( self ): |
380 |
+ files = config.get_or_fail ( 'REPO.config_files' ) |
381 |
+ for f in files: |
382 |
+ self.load_file ( f ) |
383 |
+ # --- end of load (...) --- |
384 |
+ |
385 |
+ def sync_all ( self, package_queue=None ): |
386 |
+ q = None |
387 |
+ if package_queue is None: |
388 |
+ q = list() |
389 |
+ add = q.append |
390 |
+ else: |
391 |
+ # TODO: _nowait? raises Exception when queue is full which is |
392 |
+ # good in non-threaded execution |
393 |
+ # -> timeout,.. |
394 |
+ add = q.put |
395 |
+ |
396 |
+ |
397 |
+ # !! TODO resume here. |
398 |
+ |
399 |
+ for repo in self.repos: |
400 |
+ if repo.sync() if self.sync_enabled else repo.nosync(): |
401 |
+ # scan repo and create package infos |
402 |
+ for p in repo.scan_distdir(): add ( p ) |
403 |
+ elif self.use_broken_repos: |
404 |
+ # warn and scan repo |
405 |
+ ## .. |
406 |
+ for p in repo.scan_distdir(): add ( p ) |
407 |
+ |
408 |
+ # --- end of sync_all (...) --- |
409 |
+ |
410 |
+ def __str__ ( self ): |
411 |
+ return '\n'.join ( ( str ( x ) for x in self.repos ) ) |
412 |
+ |
413 |
|
414 |
diff --git a/roverlay/remote/repoloader.py b/roverlay/remote/repoloader.py |
415 |
new file mode 100644 |
416 |
index 0000000..eae35c5 |
417 |
--- /dev/null |
418 |
+++ b/roverlay/remote/repoloader.py |
419 |
@@ -0,0 +1,66 @@ |
420 |
+ |
421 |
+import logging |
422 |
+ |
423 |
+try: |
424 |
+ import configparser |
425 |
+except ImportError as running_python2: |
426 |
+ # configparser is named ConfigParser in python2 |
427 |
+ import ConfigParser as configparser |
428 |
+ |
429 |
+ |
430 |
+from roverlay import config |
431 |
+ |
432 |
+from roverlay.remote.basicrepo import LocalRepo |
433 |
+from roverlay.remote.repo import RsyncRepo |
434 |
+ |
435 |
+LOGGER = logging.getLogger ( 'repoloader' ) |
436 |
+ |
437 |
+def read_repofile ( repo_file, lenient=False ): |
438 |
+ |
439 |
+ parser = configparser.SafeConfigParser ( allow_no_value=False ) |
440 |
+ |
441 |
+ if lenient: |
442 |
+ parser.read ( repo_file ) |
443 |
+ else: |
444 |
+ fh = None |
445 |
+ try: |
446 |
+ fh = open ( repo_file, 'r' ) |
447 |
+ parser.readfp ( fh ) |
448 |
+ finally: |
449 |
+ if fh: fh.close() |
450 |
+ |
451 |
+ repos = list() |
452 |
+ |
453 |
+ for name in parser.sections(): |
454 |
+ |
455 |
+ get = lambda a, b=None : parser.get ( name, a, raw=True, fallback=b ) |
456 |
+ |
457 |
+ repo_type = get ( 'type', 'rsync' ).lower() |
458 |
+ |
459 |
+ if repo_type == 'local': |
460 |
+ repo = LocalRepo ( |
461 |
+ name = get ( 'name', name ), |
462 |
+ directory = get ( 'directory' ), |
463 |
+ src_uri = get ( 'src_uri' ) |
464 |
+ ) |
465 |
+ elif repo_type == 'rsync': |
466 |
+ repo = RsyncRepo ( |
467 |
+ name = get ( 'name', name ), |
468 |
+ directory = get ( 'directory' ), |
469 |
+ src_uri = get ( 'src_uri' ), |
470 |
+ rsync_uri = get ( 'rsync_uri' ), |
471 |
+ base_uri = get ( 'base_uri' ), |
472 |
+ extra_rsync_opts = get ( 'extra_rsync_opts' ) |
473 |
+ ) |
474 |
+ else: |
475 |
+ LOGGER.error ( "Unknown repo type %s for %s" % ( repo_type, name ) ) |
476 |
+ continue |
477 |
+ |
478 |
+ LOGGER.debug ( 'New entry, ' + str ( repo ) ) |
479 |
+ |
480 |
+ repos.append ( repo ) |
481 |
+ repo = None |
482 |
+ |
483 |
+ |
484 |
+ return repos |
485 |
+# --- end of read_repofile (...) --- |
486 |
|
487 |
diff --git a/roverlay/remote/rsync.py b/roverlay/remote/rsync.py |
488 |
new file mode 100644 |
489 |
index 0000000..e46d1db |
490 |
--- /dev/null |
491 |
+++ b/roverlay/remote/rsync.py |
492 |
@@ -0,0 +1,86 @@ |
493 |
+import os |
494 |
+import subprocess |
495 |
+ |
496 |
+from roverlay import config |
497 |
+from roverlay.util import keepenv |
498 |
+ |
499 |
+ |
500 |
+RSYNC_ENV = keepenv ( |
501 |
+ 'PATH', |
502 |
+ 'USER', |
503 |
+ 'LOGNAME', |
504 |
+ 'RSYNC_PROXY', |
505 |
+ 'RSYNC_PASSWORD', |
506 |
+) |
507 |
+ |
508 |
+ |
509 |
+# --recursive is not in the default opts, subdirs in CRAN/contrib are |
510 |
+# either R release (2.xx.x[-patches] or the package archive) |
511 |
+DEFAULT_RSYNC_OPTS = ( |
512 |
+ '--links', # copy symlinks as symlinks, |
513 |
+ '--safe-links', # but ignore links outside of tree |
514 |
+ '--times', # |
515 |
+ '--compress', # FIXME: add lzo if necessary |
516 |
+ '--delete', # |
517 |
+ '--force', # allow deletion of non-empty dirs |
518 |
+ '--human-readable', # |
519 |
+ '--stats', # |
520 |
+ '--chmod=ugo=r,u+w,Dugo+x', # 0755 for transferred dirs, 0644 for files |
521 |
+) |
522 |
+ |
523 |
+class RsyncJob ( object ): |
524 |
+ def __init__ ( |
525 |
+ self, remote=None, distdir=None, run_now=True, extra_opts=None |
526 |
+ ): |
527 |
+ self.remote = remote |
528 |
+ self.distdir = distdir |
529 |
+ self.extra_opts = None |
530 |
+ |
531 |
+ if run_now: self.run() |
532 |
+ # --- end of __init__ (...) --- |
533 |
+ |
534 |
+ def _rsync_argv ( self ): |
535 |
+ if self.remote is None or self.distdir is None: |
536 |
+ raise Exception ( "None in (remote,distdir)." ) |
537 |
+ |
538 |
+ argv = [ 'rsync' ] |
539 |
+ |
540 |
+ argv.extend ( DEFAULT_RSYNC_OPTS ) |
541 |
+ |
542 |
+ max_bw = config.get ( 'RSYNC_BWLIMIT', None ) |
543 |
+ if max_bw is not None: |
544 |
+ argv.append ( '--bwlimit=%i' % max_bw ) |
545 |
+ |
546 |
+ if self.extra_opts is not None: |
547 |
+ if isinstance ( self.extra_opts, str ) or \ |
548 |
+ not hasattr ( self.extra_opts, '__iter__' )\ |
549 |
+ : |
550 |
+ argv.append ( self.extra_opts ) |
551 |
+ else: |
552 |
+ argv.extend ( self.extra_opts ) |
553 |
+ |
554 |
+ argv.extend ( ( self.remote, self.distdir ) ) |
555 |
+ |
556 |
+ return argv |
557 |
+ # --- end of _rsync_argv (...) --- |
558 |
+ |
559 |
+ def run ( self ): |
560 |
+ |
561 |
+ rsync_cmd = self._rsync_argv() |
562 |
+ |
563 |
+ os.makedirs ( self.distdir, exist_ok=True ) |
564 |
+ |
565 |
+ # TODO pipe/log/.., running this in blocking mode until implemented |
566 |
+ |
567 |
+ proc = subprocess.Popen ( |
568 |
+ rsync_cmd, |
569 |
+ stdin=None, stdout=None, stderr=None, |
570 |
+ env=RSYNC_ENV |
571 |
+ ) |
572 |
+ |
573 |
+ if proc.communicate() != ( None, None ): |
574 |
+ raise AssertionError ( "expected None,None from communicate!" ) |
575 |
+ |
576 |
+ self.returncode = proc.returncode |
577 |
+ |
578 |
+ # --- end of start (...) --- |