#!/usr/bin/env python
"""
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

----------------------------------------------------------------------------

Author:      eliott <eliott [at] cactuswax.net>
Version:     
Changset-ID:  (mercurial hash id)
Project Url: http://e.cactuswax.net/cactus/projects/pygenrepo/
"""
import os, glob, tarfile, time, stat, md5, cStringIO
import logging
from optparse import OptionParser

### Small section of variables
VERSION = 'devel'


class MyRepo:
   """ Class for managing an Archlinux repository tarball. """
   def __init__(self,path):
      self.path = path
      self.tar = None
      
   def open(self):
      """ Open/create the repository tarball. Open for gzip writing. """
      self.tar = tarfile.open(self.path,"w:gz")
   
   def close(self):
      """ Close the repository tarball, and write any pending changes. """
      self.tar.close()
      
   def addPackage(self, archpackage):
      """ Add a package to the repository tarball.
          Takes a single argument of and Archlinux package name. Package name 
          must exist in the current directory. 
      """
      if not self.tar:
         self.open()
      apackage = ArchPackage(archpackage)
      self._addDir('%s/' %  apackage.name)
      self._addFileByString('%s/desc'    % apackage.name, apackage.getDescriptionString())
      self._addFileByString('%s/depends' % apackage.name, apackage.getDependsString())
      
   def _addDir(self, path):
      """ Private method. Adds a directory to the repository. 
          Takes a single argument of a pathname to add as a directory. 
      """
      tarinfo = MyTarInfo(path)
      tarinfo.setDirType()
      self.tar.addfile(tarinfo, None)
      
   def _addFileByString(self, path, content):
      """ Private method. Adds a file to the repository.
          Takes two arguments. A pathname, and the file content string buffer. 
      """
      tarinfo = MyTarInfo(path)
      pfile = cStringIO.StringIO()
      pfile.write(content)
      size = pfile.tell()
      pfile.seek(0)
      tarinfo.setFileType(size)
      self.tar.addfile(tarinfo, pfile)
      pfile.close()
      

class MyTarInfo(tarfile.TarInfo):
   """ Class that extends the Tarinfo class, to ease creation of custom tar 
       contents. 
   """
   def __init__(self,name):
      tarfile.TarInfo.__init__(self,name)
      self.mode = 0755
      self.mtime = time.time()
      self.uid = 0
      self.gid = 0
      self.uname = "root"
      self.gname = "root"
      
   def setFileType(self,size):
      """ Method to set the type of the tarinfo to a file.
          Takes a single integer argument, to set the filesize. 
      """
      self.type = tarfile.REGTYPE
      self.size = size
      
   def setDirType(self):
      """ Method to set the type of the tarinfo to a directory. """
      self.type = tarfile.DIRTYPE
      
class ArchPackage:
   """ Class used as a storage object for an Archlinux package. """
   def __init__(self, path):
      self.name = path.rstrip('.pkg.tar.gz')
      self.path = path
      self.parsed = False
      self.desc = None
      self.size = 0
      self.depends = None
   
   def _parse_pkginfo(self):
      """ Private method. Used to parse the data contents of a Archlinux 
          package. 
      """
      vars = { 'pkgname'  : None,
               'pkgver'   : None,
               'pkgdesc'  : None,
               'group'    : [],
               'replaces' : [],
               'depend'   : [],
               'conflict' : [],
               'provides' : [] }

      self.size = os.stat(self.path)[stat.ST_SIZE]
      fp = open(self.path, 'r')
      tarball = tarfile.open(fp.name,'r',fp)
      pkginfo = tarball.extractfile('.PKGINFO')
      
      for line in pkginfo.readlines():
         if line.find('#') >= 0:
            line = line[0:line.index('#')]
         if len(line) > 0:
            (var,val) = line.split('=',1)
            var = var.strip()
            val = val.strip()
            if var == None or var not in vars:
               continue
            elif var == 'depend' or var == 'conflict' or var == 'provides' or var == 'replaces' or var == 'group':
               vars[var].append(val)
            else:
               vars[var] = val              

      desc = []
      if vars['pkgname'] : desc.append('%%NAME%%\n%s' % vars['pkgname'])
      if vars['pkgver']  : desc.append('%%VERSION%%\n%s' % vars['pkgver'])
      if vars['pkgdesc'] : desc.append('%%DESC%%\n%s' % vars['pkgdesc']) 
      desc.append('%%CSIZE%%\n%s' % self.size) 
      desc.append('%%MD5SUM%%\n%s' % self._make_md5(fp))
      if ('groups' in vars) and (len(vars['groups']) > 0):
         desc.append('%%GROUPS%%\n%s' % '\n'.join(vars['groups'])) 
      if ('replaces' in vars) and (len(vars['replaces']) > 0):
         desc.append('%%REPLACES%%\n%s' % '\n'.join(vars['replaces'])) 
      desc.append('') ## make sure there is enough trailing whitespace
      self.desc = '\n\n'.join(desc)
      
      depends = []
      if ('depend' in vars) and (len(vars['depend']) > 0):
         depends.append('%%DEPENDS%%\n%s' % '\n'.join(vars['depend']))
      if ('conflicts' in vars) and (len(vars['conflicts']) > 0):
         depends.append('%%CONFLICTS%%\n%s' % '\n'.join(vars['conflicts']))     
      if ('provides' in vars) and (len(vars['provides']) > 0):
         depends.append('%%PROVIDES%%\n%s' % '\n'.join(vars['provides']))
      depends.append('') ## make sure there is enough trailing whitespace
      self.depends = '\n\n'.join(depends)
      
      tarball.close()
      fp.close()
      self.parsed = True

   def _make_md5(self,fileobject):
      """ Private method. Used to create the md5sum hash of the package.
          Takes a single argument of type fileobject. This object is read to 
          compute the hash. 
          Returns the hash as a string of hex. 
      """
      current_pos = fileobject.tell()
      m = md5.new()
      fileobject.seek(0)      
      while 1:
         data = fileobject.read(2048)
         if not data:
            break
         m.update(data)
      fileobject.seek(current_pos)
      return m.hexdigest()
   
   def getDescriptionString(self):
      """ Returns the package description information as a string. """
      if not self.parsed:
         self._parse_pkginfo()
      return self.desc
   
   def getDependsString(self):
      """ Returns the package dependency information as a string. """
      if not self.parsed:
         self._parse_pkginfo()
      return self.depends


if __name__ == '__main__':
   parser = OptionParser(version='%%prog %s' % VERSION)
   parser.set_defaults(verbose=False)
   parser.add_option('-v', '--verbose',
                     action='store_true',
                     dest='verbose',
                     help='Use more verbose output.')

   parser.add_option('-r', '--repo-name', 
                     action='store',
                     dest='reponame', 
                     help='The name of the repository to generate.', 
                     nargs=1)

   (options, args) = parser.parse_args()
   logging.basicConfig(level=logging.INFO,format='%(message)s')
   if options.verbose:
      logging.getLogger().setLevel(logging.DEBUG)

   if not options.reponame:
      parser.error('Repository name is required. \n See --help for more info.')

   try:
      import psyco
      psyco.full()
      logging.debug('Using pysco...')
   except ImportError:
      logging.debug('Psyco not found. Continuing...')
      pass

   ## get list of packages in current dir
   logging.info('Creating repository file %s.db.tar.gz' % options.reponame)
   repository = MyRepo("%s.db.tar.gz" % options.reponame)
   packages = glob.glob('*.pkg.tar.gz')
   count = 0
   for package in packages:
      logging.debug('Adding package %s' % package)
      repository.addPackage(package)
      count += 1
   logging.info('%d packages included in repository' % count)
   repository.close()


