version 0.6
[opensuse:osc.git] / osc / core.py
1 #!/usr/bin/python
2
3 # Copyright (C) 2006 Peter Poeml.  All rights reserved.
4 # This program is free software; it may be used, copied, modified
5 # and distributed under the terms of the GNU General Public Licence,
6 # either version 2, or (at your option) any later version.
7
8 __version__ = '0.6'
9
10 import os
11 import sys
12 import urllib2
13 from urlparse import urlunsplit
14 import cElementTree as ET
15 from cStringIO import StringIO
16 import shutil
17
18
19 from xml.dom.ext.reader import Sax2
20 from xml.dom.ext import PrettyPrint
21
22 netloc = 'api.opensuse.org'
23 scheme = 'http'
24
25 BUFSIZE = 1024*1024
26 store = '.osc'
27 exclude_stuff = [store, '.svn', 'CVS']
28
29
30 new_project_templ = """\
31 <project name="%s">
32   <title>Short title of NewProject</title>
33   <description>This project aims at providing some foo and bar.
34
35 It also does some weird stuff.
36 </description>
37   <person role="maintainer" userid="%s" />
38 </project>
39                         """
40
41 new_package_templ = """\
42 <package name="%s">
43   <title>Title of NewPackage</title>
44   <description>DESCIPTION</description>
45   <person role="maintainer" userid="%s"/>
46 </package>
47 """
48
49
50 class File:
51     """represent a file, including its metadata"""
52     def __init__(self, name, md5, size, mtime):
53         self.name = name
54         self.md5 = md5
55         self.size = size
56         self.mtime = mtime
57     def __str__(self):
58         return self.name
59
60
61 class Project:
62     """represent a project directory, holding packages"""
63     def __init__(self, dir):
64         self.dir = dir
65         if self.dir.startswith('/'):
66             self.absdir = dir
67         elif self.dir == os.curdir:
68             self.absdir = os.getcwd()
69         else:
70             self.absdir = os.path.join(os.getcwd(), dir)
71
72         self.name = store_read_project(self.dir)
73
74         self.pacs_available = meta_get_packagelist(self.name)
75
76         self.pacs_have = []
77         for i in os.listdir(self.dir):
78             if i in self.pacs_available: 
79                 self.pacs_have.append(i)
80
81         self.pacs_missing = []
82         for i in self.pacs_available:
83             if i not in self.pacs_have:
84                 self.pacs_missing.append(i)
85
86     def checkout_missing_pacs(self):
87         for pac in self.pacs_missing:
88             print 'checking out new package %s' % pac
89             olddir = os.getcwd()
90             os.chdir(os.pardir)
91             checkout_package(self.name, pac)
92             os.chdir(olddir)
93
94
95     def __str__(self):
96         r = []
97         r.append('*****************************************************')
98         r.append('Project %s (dir=%s, absdir=%s)' % (self.name, self.dir, self.absdir))
99         r.append('have pacs:\n%s' % ', '.join(self.pacs_have))
100         r.append('missing pacs:\n%s' % ', '.join(self.pacs_missing))
101         r.append('*****************************************************')
102         return '\n'.join(r)
103
104
105
106 class Package:
107     """represent a package (its directory) and read/keep/write its metadata"""
108     def __init__(self, workingdir):
109         self.dir = workingdir
110         self.storedir = os.path.join(self.dir, store)
111
112         check_store_version(self.dir)
113
114         self.prjname = store_read_project(self.dir)
115         self.name = store_read_package(self.dir)
116
117         files_tree = read_filemeta(self.dir)
118         files_tree_root = files_tree.getroot()
119
120         self.rev = files_tree_root.get('rev')
121
122         self.filenamelist = []
123         self.filelist = []
124         for node in files_tree_root.findall('entry'):
125             try: 
126                 f = File(node.get('name'), 
127                          node.get('md5'), 
128                          int(node.get('size')), 
129                          int(node.get('mtime')))
130             except: 
131                 # okay, a very old version of _files, which didn't contain any metadata yet... 
132                 f = File(node.get('name'), '', 0, 0)
133             self.filelist.append(f)
134             self.filenamelist.append(f.name)
135
136         self.to_be_deleted = read_tobedeleted(self.dir)
137         self.in_conflict = read_inconflict(self.dir)
138
139         self.todo = []
140         self.todo_send = []
141         self.todo_delete = []
142
143         # gather unversioned files (the ones not listed in _meta)
144         self.filenamelist_unvers = []
145         for i in os.listdir(self.dir):
146             if i in exclude_stuff:
147                 continue
148             if not i in self.filenamelist:
149                 self.filenamelist_unvers.append(i) 
150
151     def addfile(self, n):
152         st = os.stat(os.path.join(self.dir, n))
153         f = File(n, None, st[6], st[8])
154         self.filelist.append(f)
155         self.filenamelist.append(n)
156         self.filenamelist_unvers.remove(n) 
157         shutil.copy2(os.path.join(self.dir, n), os.path.join(self.storedir, n))
158         
159     def delete_localfile(self, n):
160         try: os.unlink(os.path.join(self.dir, n))
161         except: pass
162         try: os.unlink(os.path.join(self.storedir, n))
163         except: pass
164
165     def put_on_deletelist(self, n):
166         if n not in self.to_be_deleted:
167             self.to_be_deleted.append(n)
168
169     def put_on_conflictlist(self, n):
170         if n not in self.in_conflict:
171             self.in_conflict.append(n)
172
173     def clear_from_conflictlist(self, n):
174         """delete an entry from the file, and remove the file if it would be empty"""
175         if n in self.in_conflict:
176
177             filename = os.path.join(self.dir, n)
178             storefilename = os.path.join(self.storedir, n)
179             myfilename = os.path.join(self.dir, n + '.mine')
180             upfilename = os.path.join(self.dir, n + '.r' + self.rev)
181
182             os.unlink(myfilename)
183             os.rename(upfilename, storefilename)
184
185             self.in_conflict.remove(n)
186
187             self.write_conflictlist()
188
189     def write_deletelist(self):
190         if len(self.to_be_deleted) == 0:
191             try:
192                 os.unlink(os.path.join(self.storedir, '_to_be_deleted'))
193             except:
194                 pass
195         else:
196             fname = os.path.join(self.storedir, '_to_be_deleted')
197             f = open(fname, 'w')
198             f.write('\n'.join(self.to_be_deleted))
199             f.write('\n')
200             f.close()
201
202     def delete_source_file(self, n):
203         import othermethods
204         
205         u = makeurl(['source', self.prjname, self.name, n])
206         othermethods.delfile(u, n, username, password)
207
208         self.delete_localfile(n)
209
210     def put_source_file(self, n):
211         import othermethods
212         
213         u = makeurl(['source', self.prjname, self.name, n])
214         othermethods.putfile(u, os.path.join(self.dir, n), username, password)
215
216         shutil.copy2(os.path.join(self.dir, n), os.path.join(self.storedir, n))
217
218     def write_conflictlist(self):
219         if len(self.in_conflict) == 0:
220             os.unlink(os.path.join(self.storedir, '_in_conflict'))
221         else:
222             fname = os.path.join(self.storedir, '_in_conflict')
223             f = open(fname, 'w')
224             f.write('\n'.join(self.in_conflict))
225             f.write('\n')
226             f.close()
227
228     def updatefile(self, n):
229         filename = os.path.join(self.dir, n)
230         storefilename = os.path.join(self.storedir, n)
231         mtime = self.findfilebyname(n).mtime
232
233         get_source_file(self.prjname, self.name, n, targetfilename=filename)
234         os.utime(filename, (-1, mtime))
235
236         shutil.copy2(filename, storefilename)
237
238     def mergefile(self, n):
239         filename = os.path.join(self.dir, n)
240         storefilename = os.path.join(self.storedir, n)
241         myfilename = os.path.join(self.dir, n + '.mine')
242         upfilename = os.path.join(self.dir, n + '.r' + self.rev)
243         os.rename(filename, myfilename)
244
245         get_source_file(self.prjname, self.name, n, targetfilename=upfilename)
246
247         ret = os.system('cd %s; diff3 -m -E %s %s %s > %s' \
248             % (self.dir, myfilename, storefilename, upfilename, filename))
249         if ret == 0:
250             # merge was successful... clean up
251             os.rename(upfilename, filename)
252             shutil.copy2(filename, storefilename)
253             os.unlink(myfilename)
254             return 'G'
255         else:
256             # unsuccessful merge
257             self.in_conflict.append(n)
258             self.write_conflictlist()
259             return 'C'
260
261
262
263     def update_filesmeta(self):
264         meta = ''.join(show_files_meta(self.prjname, self.name))
265         f = open(os.path.join(self.storedir, '_files'), 'w')
266         f.write(meta)
267         f.close()
268         
269     def update_pacmeta(self):
270         meta = ''.join(show_package_meta(self.prjname, self.name))
271         f = open(os.path.join(self.storedir, '_meta'), 'w')
272         f.write(meta)
273         f.close()
274
275     def findfilebyname(self, n):
276         for i in self.filelist:
277             if i.name == n:
278                 return i
279
280     def status(self, n):
281         """
282         status can be:
283
284          file  storefile  file present  STATUS
285         exists  exists      in _files
286
287           x       x            -        'A'
288           x       x            x        ' ' if digest differs: 'M'
289                                             and if in conflicts file: 'C'
290           x       -            -        '?'
291           x       -            x        'D' and listed in _to_be_deleted
292           -       x            x        '!'
293           -       x            -        'D' (when file in working copy is already deleted)
294           -       -            x        'F' (new in repo, but not yet in working copy)
295           -       -            -        NOT DEFINED
296
297         """
298
299         known_by_meta = False
300         exists = False
301         exists_in_store = False
302         if n in self.filenamelist:
303             known_by_meta = True
304         if os.path.exists(os.path.join(self.dir, n)):
305             exists = True
306         if os.path.exists(os.path.join(self.storedir, n)):
307             exists_in_store = True
308
309
310         if exists and not exists_in_store and known_by_meta:
311             state = 'D'
312         elif n in self.to_be_deleted:
313             state = 'D'
314         elif n in self.in_conflict:
315             state = 'C'
316         elif exists and exists_in_store and known_by_meta:
317             #print self.findfilebyname(n)
318             if dgst(os.path.join(self.dir, n)) != self.findfilebyname(n).md5:
319                 state = 'M'
320             else:
321                 state = ' '
322         elif exists and not exists_in_store and not known_by_meta:
323             state = '?'
324         elif exists and exists_in_store and not known_by_meta:
325             state = 'A'
326         elif not exists and exists_in_store and known_by_meta:
327             state = '!'
328         elif not exists and not exists_in_store and known_by_meta:
329             state = 'F'
330         elif not exists and exists_in_store and not known_by_meta:
331             state = 'D'
332         elif not exists and not exists_in_store and not known_by_meta:
333             print '%s: not exists and not exists_in_store and not nown_by_meta' % n
334             print 'this code path should never be reached!'
335             sys.exit(1)
336         
337         return state
338
339
340     def merge(self, otherpac):
341         self.todo += otherpac.todo
342
343     def __str__(self):
344         r = """
345 name: %s
346 prjname: %s
347 workingdir: %s
348 localfilelist: %s
349 rev: %s
350 'todo' files: %s
351 """ % (self.name, 
352         self.prjname, 
353         self.dir, 
354         '\n               '.join(self.filenamelist), 
355         self.rev, 
356         self.todo)
357
358         return r
359
360
361 def is_project_dir(d):
362     if os.path.exists(os.path.join(d, store, '_project')) and not \
363        os.path.exists(os.path.join(d, store, '_package')):
364         return True
365     else:
366         return False
367
368         
369 def findpacs(files):
370     pacs = []
371     for f in files:
372         if f in exclude_stuff:
373             break
374
375         p = filedir_to_pac(f)
376         known = None
377         for i in pacs:
378             if i.name == p.name:
379                 known = i
380                 break
381         if known:
382             i.merge(p)
383         else:
384             pacs.append(p)
385     return pacs
386         
387
388 def read_filemeta(dir):
389     return ET.parse(os.path.join(dir, store, '_files'))
390
391
392 def read_tobedeleted(dir):
393     r = []
394     fname = os.path.join(dir, store, '_to_be_deleted')
395
396     if os.path.exists(fname):
397
398         for i in open(fname, 'r').readlines():
399             r.append(i.strip())
400
401     return r
402
403
404 def read_inconflict(dir):
405     r = []
406     fname = os.path.join(dir, store, '_in_conflict')
407
408     if os.path.exists(fname):
409
410         for i in open(fname, 'r').readlines():
411             r.append(i.strip())
412
413     return r
414
415
416 def parseargs(list_of_args):
417         if list_of_args:
418             return list_of_args
419         else:
420             return [ os.curdir ]
421
422
423 def filedir_to_pac(f):
424
425     if os.path.isdir(f):
426         wd = f
427         p = Package(wd)
428
429     elif os.path.isfile(f):
430         wd = os.path.dirname(f)
431         if wd == '':
432             wd = os.curdir
433         p = Package(wd)
434         p.todo = [ os.path.basename(f) ]
435
436     else:
437         wd = os.path.dirname(f)
438         if wd == '':
439             wd = os.curdir
440         p = Package(wd)
441         p.todo = [ os.path.basename(f) ]
442         
443
444     #else:
445     #    print 
446     #    print 'error: %s is neither a valid file or directory' % f
447     #    sys.exit(1)
448
449     return p
450
451
452 def statfrmt(statusletter, filename):
453     return '%s    %s' % (statusletter, filename)
454
455
456 def makeurl(l):
457     """given a list of path compoments, construct a complete URL"""
458     return urlunsplit((scheme, netloc, '/'.join(l), '', ''))               
459
460
461 def readauth():
462     """look for the credentials. If there aren't any, ask and store them"""
463
464     #
465     # try .netrc first
466     #
467
468     # the needed entry in .netrc looks like this:
469     # machine api.opensuse.org login your_login password your_pass
470     # but it is not able for credentials containing spaces
471     import netrc
472     global username, password
473
474     try:
475         info = netrc.netrc()
476         username, account, password = info.authenticators(netloc)
477         return username, password
478
479     except (IOError, TypeError):
480         pass
481
482     #
483     # try .oscrc next
484     #
485     import ConfigParser
486     conffile = os.path.expanduser('~/.oscrc')
487     if os.path.exists(conffile):
488         config = ConfigParser.ConfigParser()
489         config.read(conffile)
490         username = config.get(netloc, 'user')
491         password = config.get(netloc, 'pass')
492         return username, password
493
494     #
495     # create .oscrc
496     #
497     import getpass
498     print >>sys.stderr, \
499 """your user account / password are not configured yet.
500 You will be asked for them below, and they will be stored in
501 %s for later use.
502 """ % conffile
503
504     username = raw_input('Username: ')
505     password = getpass.getpass()
506
507     fd = open(conffile, 'w')
508     os.chmod(conffile, 0600)
509     print >>fd, '[%s]\nuser: %s\npass: %s' % (netloc, username, password)
510     fd.close()
511         
512     return username, password
513         
514
515
516 def init_basicauth():
517
518     username, password = readauth()
519
520     passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
521     # this creates a password manager
522     passmgr.add_password(None, netloc, username, password)
523     # because we have put None at the start it will always
524     # use this username/password combination for  urls
525     # for which `netloc` is a super-url
526
527     authhandler = urllib2.HTTPBasicAuthHandler(passmgr)
528     # create the AuthHandler
529
530     opener = urllib2.build_opener(authhandler)
531
532     urllib2.install_opener(opener)
533     # All calls to urllib2.urlopen will now use our handler
534     # Make sure not to include the protocol in with the URL, or
535     # HTTPPasswordMgrWithDefaultRealm will be very confused.
536     # You must (of course) use it when fetching the page though.
537
538
539 def init_package_dir(project, package, dir):
540     if not os.path.isdir(store):
541         os.mkdir(store)
542     os.chdir(store)
543     f = open('_project', 'w')
544     f.write(project + '\n')
545     f.close
546     f = open('_package', 'w')
547     f.write(package + '\n')
548     f.close
549
550     f = open('_files', 'w')
551     f.write(''.join(show_files_meta(project, package)))
552     f.close()
553
554     f = open('_osclib_version', 'w')
555     f.write(__version__ + '\n')
556     f.close()
557
558     os.chdir(os.pardir)
559     return
560
561
562 def check_store_version(dir):
563     versionfile = os.path.join(dir, store, '_osclib_version')
564     try:
565         v = open(versionfile).read().strip()
566     except:
567         v = ''
568
569     if v == '':
570         print 'error: "%s" is not an osc working copy' % dir
571         sys.exit(1)
572
573     if v != __version__:
574         if v in ['0.2', '0.3', '0.4', '0.5']:
575             # version is fine, no migration needed
576             f = open(versionfile, 'w')
577             f.write(__version__ + '\n')
578             f.close()
579             return 
580         print 
581         print 'the osc metadata of your working copy "%s"' % dir
582         print 'has the wrong version (%s), should be %s' % (v, __version__)
583         print 'please do a fresh checkout'
584         print 
585         sys.exit(1)
586     
587
588 def meta_get_packagelist(prj):
589
590     u = makeurl(['source', prj, '_meta'])
591     f = urllib2.urlopen(u)
592
593     tree = ET.parse(f)
594     root = tree.getroot()
595
596     r = []
597     for node in root.findall('package'):
598         r.append(node.get('name'))
599     return r
600
601
602 def meta_get_filelist(prj, package):
603
604     u = makeurl(['source', prj, package])
605     f = urllib2.urlopen(u)
606     tree = ET.parse(f)
607
608     r = []
609     for node in tree.getroot():
610         r.append(node.get('name'))
611     return r
612
613
614 def localmeta_addfile(filename):
615
616     if filename in localmeta_get_filelist():
617         return
618
619     reader = Sax2.Reader()
620     f = open(os.path.join(store, '_files')).read()
621     doc = reader.fromString(f)
622
623     new = doc.createElement('entry')
624     #new.setAttribute('filetype', 'source')
625     new.setAttribute('name', filename)
626     doc.documentElement.appendChild(new)
627
628     o = open(os.path.join(store, '_files'), 'w')
629     PrettyPrint(doc, stream=o)
630     o.close()
631
632     
633 def localmeta_removefile(filename):
634
635     reader = Sax2.Reader()
636     f = open(os.path.join(store, '_files')).read()
637     doc = reader.fromString(f)
638
639     for i in doc.getElementsByTagName('entry'):
640         if i.getAttribute('name') == filename:
641             i.parentNode.removeChild(i)
642
643     o = open(os.path.join(store, '_files'), 'w')
644     PrettyPrint(doc, stream=o)
645     o.close()
646     
647
648 def localmeta_get_filelist():
649
650     tree = ET.parse(os.path.join(store, '_files'))
651     root = tree.getroot()
652
653     r = []
654     for node in root.findall('entry'):
655         r.append(node.get('name'))
656     return r
657
658
659 def get_slash_source():
660     u = makeurl(['source'])
661     tree = ET.parse(urllib2.urlopen(u))
662
663     r = []
664     for node in tree.getroot():
665         r.append(node.get('name'))
666     r.sort()
667     return r
668
669
670 def show_project_meta(prj):
671     f = urllib2.urlopen(makeurl(['source', prj, '_meta']))
672     return f.readlines()
673
674
675 def show_package_meta(prj, pac):
676     f = urllib2.urlopen(makeurl(['source', prj, pac, '_meta']))
677     return f.readlines()
678
679
680 def edit_meta(prj, pac):
681     import othermethods
682     import tempfile
683
684     (f, filename) = tempfile.mkstemp(prefix = 'osc_editmeta.', suffix = '.xml', dir = '/tmp')
685
686     if pac:
687         u = makeurl(['source', prj, pac, '_meta'])
688         try:
689             m = show_package_meta(prj, pac)
690         except urllib2.HTTPError, e:
691             if e.code == 404:
692                 m = new_package_templ % (pac, username)
693
694     else:
695         u = makeurl(['source', prj, '_meta'])
696         try:
697             m = show_project_meta(prj)
698         except urllib2.HTTPError, e:
699             if e.code == 404:
700                 m = new_project_templ % (prj, username)
701
702     f = open(filename, 'w')
703     f.write(''.join(m))
704     f.close()
705
706     timestamp = os.stat(filename).st_mtime
707
708     editor = os.getenv('EDITOR', default='vim')
709     os.system('%s %s' % (editor, filename))
710
711     if os.stat(filename).st_mtime == timestamp:
712         print 'File unchanged. Not saving.'
713         os.unlink(filename)
714
715     else:
716         print 'Sending meta data...', 
717         othermethods.putfile(u, filename, username, password)
718         os.unlink(filename)
719         print 'Done.'
720
721
722
723 def show_files_meta(prj, pac):
724     f = urllib2.urlopen(makeurl(['source', prj, pac]))
725     return f.readlines()
726
727
728 def read_meta_from_spec(specfile):
729     """read Name, Summary and %description from spec file"""
730     in_descr = False
731     descr = []
732
733     if not os.path.isfile(specfile):
734         print 'file \'%s\' is not a readable file' % specfile
735         return None
736
737     for line in open(specfile, 'r'):
738         if line.startswith('Name:'):
739             name = line.split(':')[1].strip()
740         if line.startswith('Summary:'):
741             summary = line.split(':')[1].strip()
742         if line.startswith('%description'):
743             in_descr = True
744             continue
745         if in_descr and line.startswith('%'):
746             break
747         if in_descr:
748             descr.append(line)
749     
750     return name, summary, descr
751
752
753 def get_user_id(user):
754     u = makeurl(['person', user.replace(' ', '+')])
755     try:
756         f = urllib2.urlopen(u)
757         return ''.join(f.readlines())
758     except urllib2.HTTPError:
759         print 'user \'%s\' not found' % user
760         return None
761
762
763 def get_source_file(prj, package, filename, targetfilename=None):
764     u = makeurl(['source', prj, package, filename])
765     #print 'checking out', u
766     f = urllib2.urlopen(u)
767
768     o = open(targetfilename or filename, 'w')
769     while 1:
770         buf = f.read(BUFSIZE)
771         if not buf: break
772         o.write(buf)
773     o.close()
774
775
776 def dgst(file):
777
778     #if not os.path.exists(file):
779         #return None
780
781     import md5
782     s = md5.new()
783     f = open(file, 'r')
784     while 1:
785         buf = f.read(BUFSIZE)
786         if not buf: break
787         s.update(buf)
788     return s.hexdigest()
789
790
791 def get_source_file_diff_upstream(prj, package, filename):
792     url = makeurl(['source', prj, package, filename])
793     f = urllib2.urlopen(url)
794
795     localfile = open(filename, 'r')
796
797     import difflib
798     #print url
799     d = difflib.unified_diff(f.readlines(), localfile.readlines(), fromfile = url, tofile = filename)
800
801     localfile.close()
802
803     return ''.join(d)
804
805
806 def get_source_file_diff(dir, filename, rev):
807     import difflib
808
809     file1 = os.path.join(dir, store, filename)  # stored original
810     file2 = os.path.join(dir, filename)         # working copy
811
812     f1 = open(file1, 'r')
813     f2 = open(file2, 'r')
814
815     d = difflib.unified_diff(\
816         f1.readlines(), \
817         f2.readlines(), \
818         fromfile = '%s     (revision %s)' % (filename, rev), \
819         tofile = '%s     (working copy)' % filename)
820
821     f1.close()
822     f2.close()
823
824     return ''.join(d)
825
826
827 def make_dir(project, package):
828     #print "creating directory '%s'" % project
829     if not os.path.exists(project):
830         print statfrmt('A', project)
831         os.mkdir(project)
832         os.mkdir(os.path.join(project, store))
833
834         f = open(os.path.join(project, store, '_project'), 'w')
835         f.write(project + '\n')
836         f.close()
837
838     #print "creating directory '%s/%s'" % (project, package)
839     if not os.path.exists(os.path.join(project, package)):
840         print statfrmt('A', '%s/%s' % (project, package))    
841         os.mkdir(os.path.join(project, package))
842         os.mkdir(os.path.join(project, package, store))
843
844     return(os.path.join(project, package))
845
846
847 def checkout_package(project, package):
848     olddir = os.getcwd()
849
850     os.chdir(make_dir(project, package))
851     init_package_dir(project, package, store)
852     p = Package(os.curdir)
853
854     for filename in p.filenamelist:
855         p.updatefile(filename)
856         print 'A   ', os.path.join(project, package, filename)
857
858     os.chdir(olddir)
859
860
861 def get_platforms():
862     f = urllib2.urlopen(makeurl(['platform']))
863     tree = ET.parse(f)
864     r = []
865     for node in tree.getroot():
866         r.append(node.get('name'))
867     r.sort()
868     return r
869
870
871 def get_platforms_of_project(prj):
872     f = show_project_meta(prj)
873     tree = ET.parse(StringIO(''.join(f)))
874
875     r = []
876     for node in tree.findall('repository'):
877         r.append(node.get('name'))
878     return r
879
880
881 def show_results_meta(prj, package, platform):
882     u = makeurl(['result', prj, platform, package, 'result'])
883     f = urllib2.urlopen(u)
884     return f.readlines()
885
886
887 def get_results(prj, package, platform):
888     #print '----------------------------------------'
889
890     r = []
891     #result_line_templ = '%(prj)-15s %(pac)-15s %(rep)-15s %(arch)-10s %(status)s'
892     result_line_templ = '%(rep)-15s %(arch)-10s %(status)s'
893
894     f = show_results_meta(prj, package, platform)
895     tree = ET.parse(StringIO(''.join(f)))
896
897     root = tree.getroot()
898
899     rmap = {}
900     rmap['prj'] = root.get('project')
901     rmap['pac'] = root.get('package')
902     rmap['rep'] = root.get('repository')
903
904     for node in root.findall('archresult'):
905         rmap['arch'] = node.get('arch')
906
907         statusnode =  node.find('status')
908         rmap['status'] = statusnode.get('code')
909
910         if rmap['status'] == 'expansion error':
911             rmap['status'] += ': ' + statusnode.find('summary').text
912
913         if rmap['status'] == 'failed':
914             rmap['status'] += ': %s://%s' % (scheme, netloc) + \
915                 '/result/%(prj)s/%(rep)s/%(pac)s/%(arch)s/log' % rmap
916
917         r.append(result_line_templ % rmap)
918     return r
919
920
921 def get_log(prj, package, platform, arch):
922     u = makeurl(['result', prj, platform, package, arch, 'log'])
923     f = urllib2.urlopen(u)
924     return f.readlines()
925
926
927 def get_history(prj, package):
928     # http://api.opensuse.org/rpm/Apache/factory/i586/apache2/history ?
929     # http://api.opensuse.org/package/Apache/apache2/history ?
930     u = makeurl(['package', prj, package, 'history'])
931     print u
932     f = urllib2.urlopen(u)
933     return f.readlines()
934
935
936 def store_read_project(dir):
937     p = open(os.path.join(dir, store, '_project')).readlines()[0].strip()
938     return p
939
940
941 def store_read_package(dir):
942     p = open(os.path.join(dir, store, '_package')).readlines()[0].strip()
943     return p
944
945 def get_osc_version():
946     return __version__
947