Gentoo Archives: gentoo-commits

From: "André Erdmann" <dywi@×××××××.de>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/R_overlay:master commit in: roverlay/remote/
Date: Mon, 25 Jun 2012 18:20:16
Message-Id: 1340648138.9a0b0c9e1740e88ee2426fefa2c97fb7e629bdb1.dywi@gentoo
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 (...) ---