# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0:
# License: GPL2 or later see COPYING
# Written by Michael Brown
# Copyright (C) 2007 Michael E Brown <mebrown@michaels-house.net>

# python library imports
import fcntl
import time
import os
import glob

# our imports
from kinkybuild.trace_decorator import decorate, traceLog, getLog
import kinkybuild.util
from kinkybuild.mounts import BindMountPoint

# set up logging, module options
requires_api_version = "1.0"

# plugin entry point
decorate(traceLog())
def init(rootObj, conf):
    PacmanCache(rootObj, conf)

# classes
class PacmanCache(object):
    """caches root environment in a tarball"""
    decorate(traceLog())
    def __init__(self, rootObj, conf):
        self.rootObj = rootObj
        self.pacman_cache_opts = conf
        self.pacmanSharedCachePath = self.pacman_cache_opts['dir'] % self.pacman_cache_opts
        self.online = rootObj.online
        rootObj.pacman_cacheObj = self
        rootObj.addHook("prepacman", self._pacmanCachePreHook)
        rootObj.addHook("postpacman", self._pacmanCachePostHook)
        rootObj.addHook("preinit", self._pacmanCachePreInitHook)
        rootObj.mounts.add(BindMountPoint(srcpath=self.pacmanSharedCachePath, bindpath=rootObj.makeChrootPath('/var/cache/pacman/pkg')))
        kinkybuild.util.mkdirIfAbsent(self.pacmanSharedCachePath)
        self.pacmanCacheLock = open(os.path.join(self.pacmanSharedCachePath, "pacmancache.lock"), "a+")

    # =============
    # 'Private' API
    # =============
    # lock the shared pacman cache (when enabled) before any access
    # by pacman, and prior to cleaning it. This prevents simultaneous access from
    # screwing things up. This can possibly happen, eg. when running multiple
    # kinky instances with --uniqueext=
    decorate(traceLog())
    def _pacmanCachePreHook(self):
        try:
            fcntl.lockf(self.pacmanCacheLock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError, e:
            self.rootObj.start("Waiting for pacmancache lock")
            fcntl.lockf(self.pacmanCacheLock.fileno(), fcntl.LOCK_EX)
            self.rootObj.finish("Waiting for pacmancache lock")

    decorate(traceLog())
    def _pacmanCachePostHook(self):
        fcntl.lockf(self.pacmanCacheLock.fileno(), fcntl.LOCK_UN)

    decorate(traceLog())
    def _pacmanCachePreInitHook(self):
        getLog().info("enabled pacman cache")
        kinkybuild.util.mkdirIfAbsent(self.rootObj.makeChrootPath('/var/cache/pacman/pkg'))

        # lock so others dont accidentally use pacman cache while we operate on it.
        self._pacmanCachePreHook()

        if self.online:
            self.rootObj.start("cleaning pacman metadata")
            for (dirpath, dirnames, filenames) in os.walk(self.pacmanSharedCachePath):
                for filename in filenames:
                    fullPath = os.path.join(dirpath, filename)
                    statinfo = os.stat(fullPath)
                    file_age_days = (time.time() - statinfo.st_ctime) / (60 * 60 * 24)
                    # prune repodata so pacman redownloads.
                    # prevents certain errors where pacman gets stuck due to bad metadata
                    for ext in (".sqlite", ".xml", ".bz2", ".xz"):
                        if filename.endswith(ext) and file_age_days > self.pacman_cache_opts['max_metadata_age_days']:
                            os.unlink(fullPath)
                            fullPath = None
                            break

                    if fullPath is None: continue
                    if file_age_days > self.pacman_cache_opts['max_age_days']:
                        os.unlink(fullPath)
                        continue
            self.rootObj.finish("cleaning pacman metadata")

        self._pacmanCachePostHook()

