# libgo - script to build library.gnome.org
# Copyright (C) 2007-2009  Frederic Peters
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301  USA

import logging
import os
import re
import stat
import tarfile

from utils import version_cmp, is_version_number

class DocModule(object):
    '''Base class for documentation shipped in tarballs'''
    makefile_am = None

    filename = None
    dirname = None
    modulename = None

    related_xsl_files = None
    mtime_xslt_files = 0

    channel = None
    nightly = False
    extra_devel_releases = None

    def create_from_tar(cls, tar, tarinfo, makefile_am, nightly = False):
        self = cls()
        if tarinfo.isdir():
            self.dirname = tarinfo.name
        else:
            self.dirname = os.path.dirname(tarinfo.name)
        if makefile_am:
            self.makefile_am = makefile_am
            self.modulename = re.findall(r'(?:DOC_ID|DOC_MODULE|HELP_ID)\s?=\s?(.*)',
                                makefile_am)[0].strip()

            if '@' in self.modulename:
                logging.warning('variadic module name: %s' % self.modulename)
                # don't go interpreting autotools variables, as in this path
                # lays madness, instead simply cut out the variable, such as
                # gnome-scan-@API_VERSION@ becomes gnome-scan.
                self.modulename = self.modulename.split('@')[0].strip('-')
            # remove the version part, so libsoup-2.4 is handled just like
            # another version of libsoup
            self.modulename = re.sub('-\d+\.\d+$', '', self.modulename)
        self.version = os.path.splitext(tar.name)[0].split('-')[-1]
        if self.version.endswith('.tar'):
            self.version = self.version[:-4]

        if nightly or self.version == 'nightly':
            self.nightly = True
            self.version = 'nightly'
            self.one_dot_version = 'nightly'
        else:
            self.one_dot_version = re.match(r'\d+\.\d+', self.version).group()

        if self.related_xsl_files:
            self.mtime_xslt_files = max([os.stat(
                        os.path.join(data_dir, 'xslt', x))[stat.ST_MTIME] \
                        for x in self.related_xsl_files])
        return self
    create_from_tar = classmethod(create_from_tar)

    def extract(self, force=False):
        '''Extract a given module out of a tarball; extraction will not happen
        if the output directory already exists, unless @force is given.'''

        ext_dirname = os.path.join(app.config.private_dir, 'extracts')
        if not os.path.exists(ext_dirname):
            os.makedirs(ext_dirname)

        base_tarball_name = os.path.basename(self.filename).rsplit('-', 1)[0]

        dirname = self.dirname
        if dirname.endswith('/'):
            dirname = dirname[:-1]

        if not os.path.exists(os.path.join(ext_dirname, self.dirname)) or force:
            logging.debug('extracting %s' % self.dirname)
            tar = tarfile.open(self.filename, 'r')
            for tarinfo in tar.getmembers():
                if not os.path.split(tarinfo.name)[0].startswith(dirname):
                    continue
                dest = os.path.join(ext_dirname, tarinfo.name)
                if tarinfo.isdir() and not os.path.exists(dest):
                    os.makedirs(dest)
                elif tarinfo.isreg():
                    if not os.path.exists(os.path.dirname(dest)):
                        os.makedirs(os.path.dirname(dest))
                    open(dest, 'w').write(tar.extractfile(tarinfo).read())
            tar.close()
            return True
        return False

    def get_libgo_document(self, doc_linguas):
        try:
            doc = [x for x in app.documents if \
                    x.module == self.modulename and x.channel == self.channel][0]
        except IndexError:
            doc = app.Document()
            doc.filename = self.dirname
            doc.module = self.modulename
            doc.channel = self.channel
            doc.languages = doc_linguas
            doc.path = self.path
            doc._last_version = self.version
            doc.versions = [self.one_dot_version]
            app.documents.append(doc)
        else:
            if doc._last_version == self.version:
                # file was already processed in a previous moduleset
                return None

            if not self.nightly:
                # a more recent version may already have been installed; probably
                # because the same module is being mentioned as an extra tarball
                # with an inferior version number; don't update path in this
                # situation.  (see bug #530517 for the mention of this problem)
                if version_cmp(doc._last_version, self.version) <= 0:
                    doc._last_version = self.version
                    if int(self.one_dot_version.split('.')[1]) % 2 == 0:
                        # update path to point to the latest version (but no
                        # development versions)
                        doc.path = self.path

            if not self.one_dot_version in doc.versions:
                doc.versions.append(self.one_dot_version)
            for lang in doc_linguas:
                if not lang in doc.languages:
                    doc.languages.append(lang)

        doc.version_mapping[self.one_dot_version] = self.version

        # only keep authorised languages
        if app.config.languages:
            for lang in doc.languages[:]:
                if lang not in app.config.languages:
                    doc.languages.remove(lang)

        return doc

    def is_development_release(self):
        development_release = (int(self.one_dot_version.split('.')[1]) % 2 == 1) or (
                int(self.one_dot_version.split('.')[0]) == 0)
        if not development_release:
            development_release = app.overlay.is_development_release(
                            self.modulename, self.one_dot_version)
        return development_release

    def install_version_symlinks(self, doc):
        '''Create stable and devel symlinks'''

        if self.nightly:
            return

        if self.channel not in ('users', 'devel', 'admin'):
            return

        if not self.one_dot_version in doc.versions:
            # this version doesn't appear in available versions, probably it
            # had been depreciated
            return

        web_output_dir = app.get_module_web_output_dir(self, versioned=False)
        development_release = self.is_development_release()

        if development_release:
            keyword = 'unstable'
        else:
            keyword = 'stable'

        path = os.path.join(web_output_dir, keyword)
        installed = False
        if os.path.islink(path):
            currently_marked = os.readlink(path)
            if currently_marked == 'stable':
                currently_marked = os.readlink(os.path.join(web_output_dir, 'stable'))
            if version_cmp(self.version, currently_marked) >= 0:
                # install new symlink
                os.unlink(path)
                os.symlink(self.one_dot_version, path)
                installed = True
        else:
            os.symlink(self.one_dot_version, path)
            installed = True

        if installed:
            base_path = app.config.doc_path_template % {
                'channel': self.channel,
                'module': self.modulename }
            if not development_release:
                if doc.path == base_path + '%s/' % self.one_dot_version:
                    # set default path to use the keyword
                    doc.path = base_path + 'stable/'

                # if there is no unstable link, create it even for a stable release
                # (or if stable is newer)
                path = os.path.join(web_output_dir, 'unstable')
                if os.path.islink(path):
                    currently_unstable = os.readlink(path)
                    if currently_unstable == 'stable' or \
                            version_cmp(self.version, currently_unstable) >= 0:
                        os.unlink(path)
                        os.symlink('stable', path)
                else:
                    os.symlink('stable', path)
            elif not os.path.exists(os.path.join(web_output_dir, 'stable')):
                # update default path to have codename if there is no stable
                # release
                doc.path = base_path + 'unstable/'

            for k in doc.version_keywords.keys():
                if doc.version_keywords.get(k) == keyword:
                    del doc.version_keywords[k]
            doc.version_keywords[self.one_dot_version] = keyword

    def create_tarball_symlink(self, doc):
        tarball_dest = '../%s-html-%s.tar.gz' % (self.modulename, self.version)
        in_version_path = os.path.join(
                app.get_module_web_output_dir(self),
                '%s-html.tar.gz' % self.modulename)
        if not self.one_dot_version in in_version_path:
            return
        if os.path.islink(in_version_path):
            os.unlink(in_version_path)
        os.symlink(tarball_dest, in_version_path)

