#! /usr/bin/python
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#                    Yoshinori Okuji <yo@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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.
#
##############################################################################


"""Generate repository information on Business Templates.
"""

import tarfile
import os
import os.path
import sys
import tempfile
import shutil
import cgi

property_list = '''
title
version
revision
description
license
dependency_list
provision_list
copyright_list
'''.strip().splitlines()

bt_title_path = os.path.join('bt', 'title')

def info(message):
  """Print a message to stdout.
  """
  sys.stdout.write(message)

def err(message):
  """Print a message to stderr.
  """
  sys.stderr.write(message)

def readProperty(property_dict, property_name, property_file):
    try:
      text = property_file.read()
      if property_name.endswith('_list'):
        property_dict[property_name[:-5]] = text and text.split('\n') or []
      else:
        property_dict[property_name] = text
    finally:
      property_file.close()

def readBusinessTemplate(tar):
  """Read an archived Business Template info.
  """
  property_dict = {}
  for info in tar:
    name_list = info.name.split('/')
    if len(name_list) == 3 and name_list[1] == 'bt' and name_list[2] in property_list:
      property_file = tar.extractfile(info)
      property_name = name_list[2]
      readProperty(property_dict, property_name, property_file)

  return property_dict

def readBusinessTemplateDirectory(dir):
  """Read Business Template Directory info.
  """
  property_dict = {}
  for property_name in property_list:
    filename = os.path.join(dir, 'bt', property_name)
    if os.path.isfile(filename):
      property_file = open(filename, 'rb')
      readProperty(property_dict, property_name, property_file)

  return property_dict

def generateInformation(fd):
  os.write(fd, '<?xml version="1.0"?>\n')
  os.write(fd, '<repository>\n')

  for file in sorted(os.listdir(os.getcwd())):
    if file.endswith('.bt5'):
      info('Reading %s... ' % (file,))
      try:
        tar = tarfile.open(file, 'r:gz')
      except tarfile.TarError:
        err('An error happened in %s; skipping\n' % (file,))
        continue
      try:
        property_dict = readBusinessTemplate(tar)
      finally:
        tar.close()
    elif os.path.isfile(os.path.join(file, bt_title_path)):
      info('Reading Directory %s... ' % (file,))
      property_dict = readBusinessTemplateDirectory(file)
    else:
      continue
    property_id_list = property_dict.keys()
    property_id_list.sort()
    os.write(fd, '  <template id="%s">\n' % (file,))
    for property_id in property_id_list:
      property_value = property_dict[property_id]
      if type(property_value) == type(''):
        os.write(fd, '    <%s>%s</%s>\n' % (
              property_id, cgi.escape(property_value), property_id))
      else:
        for value in property_value:
          os.write(fd, '    <%s>%s</%s>\n' % (
                property_id, cgi.escape(value), property_id))
    os.write(fd, '  </template>\n')
    info('done\n')
  os.write(fd, '</repository>\n')

def main():
  if len(sys.argv) < 2:
    dir_list = ['.']
  else:
    dir_list = sys.argv[1:]

  cwd = os.getcwd()
  for d in dir_list:
    os.chdir(d)
    fd, path = tempfile.mkstemp()
    try:
      try:
        generateInformation(fd)
      finally:
        os.close(fd)
    except:
      os.remove(path)
      raise
    else:
      shutil.move(path, 'bt5list')
      cur_umask = os.umask(0666)
      os.chmod('bt5list', 0666 & ~cur_umask)
      os.umask(cur_umask)
    os.chdir(cwd)

# monkey patch tarfile library if python version is 2.4.
if sys.version_info[0:2] == (2, 4):
    from tarfile import BLOCKSIZE, GNUTYPE_SPARSE, SUPPORTED_TYPES, TarFile, \
         TarInfo, calc_chksum, normpath, nts

    def frombuf(cls, buf):
        """Construct a TarInfo object from a 512 byte string buffer.
        """
        tarinfo = cls()
        tarinfo.name   = nts(buf[0:100])
        tarinfo.mode   = int(buf[100:108], 8)
        tarinfo.uid    = int(buf[108:116],8)
        tarinfo.gid    = int(buf[116:124],8)

        # There are two possible codings for the size field we
        # have to discriminate, see comment in tobuf() below.
        if buf[124] != chr(0200):
            tarinfo.size = long(buf[124:136], 8)
        else:
            tarinfo.size = 0L
            for i in range(11):
                tarinfo.size <<= 8
                tarinfo.size += ord(buf[125 + i])

        tarinfo.mtime  = long(buf[136:148], 8)
        tarinfo.chksum = int(buf[148:156], 8)
        tarinfo.type   = buf[156:157]
        tarinfo.linkname = nts(buf[157:257])
        tarinfo.uname  = nts(buf[265:297])
        tarinfo.gname  = nts(buf[297:329])
        try:
            tarinfo.devmajor = int(buf[329:337], 8)
            tarinfo.devminor = int(buf[337:345], 8)
        except ValueError:
            tarinfo.devmajor = tarinfo.devmajor = 0
        tarinfo.prefix = buf[345:500]

        # The prefix field is used for filenames > 100 in
        # the POSIX standard.
        # name = prefix + '/' + name
        if tarinfo.type != GNUTYPE_SPARSE:
            tarinfo.name = normpath(os.path.join(nts(tarinfo.prefix), tarinfo.name))

        # Directory names should have a '/' at the end.
        if tarinfo.isdir() and tarinfo.name[-1:] != "/":
            tarinfo.name += "/"
        return tarinfo

    TarInfo.frombuf = classmethod(frombuf)

    def next(self):
        """Return the next member of the archive as a TarInfo object, when
           TarFile is opened for reading. Return None if there is no more
           available.
        """
        self._check("ra")
        if self.firstmember is not None:
            m = self.firstmember
            self.firstmember = None
            return m

        # Read the next block.
        self.fileobj.seek(self.offset)
        while True:
            buf = self.fileobj.read(BLOCKSIZE)
            if not buf:
                return None
            try:
                tarinfo = TarInfo.frombuf(buf)
            except ValueError:
                if self.ignore_zeros:
                    if buf.count(NUL) == BLOCKSIZE:
                        adj = "empty"
                    else:
                        adj = "invalid"
                    self._dbg(2, "0x%X: %s block" % (self.offset, adj))
                    self.offset += BLOCKSIZE
                    continue
                else:
                    # Block is empty or unreadable.
                    if self.offset == 0:
                        # If the first block is invalid. That does not
                        # look like a tar archive we can handle.
                        raise ReadError,"empty, unreadable or compressed file"
                    return None
            break

        # We shouldn't rely on this checksum, because some tar programs
        # calculate it differently and it is merely validating the
        # header block. We could just as well skip this part, which would
        # have a slight effect on performance...
        if tarinfo.chksum != calc_chksum(buf):
            self._dbg(1, "tarfile: Bad Checksum %r" % tarinfo.name)

        # Set the TarInfo object's offset to the current position of the
        # TarFile and set self.offset to the position where the data blocks
        # should begin.
        tarinfo.offset = self.offset
        self.offset += BLOCKSIZE

        # Check if the TarInfo object has a typeflag for which a callback
        # method is registered in the TYPE_METH. If so, then call it.
        if tarinfo.type in self.TYPE_METH:
            return self.TYPE_METH[tarinfo.type](self, tarinfo)

        tarinfo.offset_data = self.offset
        if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES:
            # Skip the following data blocks.
            self.offset += self._block(tarinfo.size)

        if tarinfo.isreg() and tarinfo.name[:-1] == "/":
            # some old tar programs don't know DIRTYPE
            tarinfo.type = DIRTYPE

        self.members.append(tarinfo)
        return tarinfo

    TarFile.next = next

if __name__ == "__main__":
  main()
