# -*- coding: ascii -*-

###########################################################################
# clive, video extraction utility
# Copyright (C) 2007-2008 Toni Gundogdu
#
# clive 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 0.1.2-1307 USA
###########################################################################

## The classes for writing and reading cache

import md5
import time

try:
    import sqlite3
    _sqlite3_found = 1
except ImportError:
    _sqlite3_found = 0

from clive.path import ConfigDir

__all__ = ['Cache']

## The class that wraps Cache access
class Cache:

    ## Constructor
    def __init__(self):
        if not _sqlite3_found: return
        self._conn = sqlite3.connect(ConfigDir().cachefile())
        self._cur = self._conn.cursor()
        self._upgrade_if_needed()
        try:
            self._cur.execute('create table cache(' \
                'cache_id integer primary key, ' \
                'cache_hash text not null unique, ' \
                'cache_time float not null, ' \
                'cache_title text not null, ' \
                'cache_length long not null, ' \
                'cache_xurl text not null, ' \
                'cache_vid text not null,' \
                'cache_lowq integer not null)')
        except sqlite3.OperationalError, err:
            pass # Ignore any "table exists already" errors discreetly

    ## Destructor
    def __del__(self):
        if not _sqlite3_found: return
        self._cur.execute('vacuum')
        self._cur.close()

    ## Write new cache entry
    #
    # Supresses any insertion errors quietly.
    def write(self, v_info):
        hash = md5.new(v_info['url'].strip('&fmt=18')).hexdigest()
        r = self.read(v_info['url'].strip('&fmt=18'))
        if not r:
            try:
                self._cur.execute('insert into cache(' \
                    'cache_hash, ' \
                    'cache_time, ' \
                    'cache_title, ' \
                    'cache_length, ' \
                    'cache_xurl, ' \
                    'cache_vid, ' \
                    'cache_lowq) ' \
                    'values(?,?,?,?,?,?,?)', 
                    (hash, time.time(), v_info['page_title'], 
                    v_info['length_bytes'], v_info['xurl'], v_info['v_id'],
                    v_info['low_quality']))
            except sqlite3.IntegrityError, err:
                pass # Ignore any "url hash not unique" errors discreetly
            except sqlite3.OperationalError, err:
                pass # Ignore any "database locked" etc. errors
        else: # Update existing record (e.g. if quality setting was changed)
            if r[4] != v_info['low_quality']: # [4]=low_quality
                self._cur.execute('update cache set ' \
                    'cache_time=?, ' \
                    'cache_title=?, ' \
                    'cache_length=?, ' \
                    'cache_xurl=?, ' \
                    'cache_vid=?, ' \
                    'cache_lowq=? where cache_hash=?',
                    (time.time(), v_info['page_title'], v_info['length_bytes'],
                    v_info['xurl'], v_info['v_id'], v_info['low_quality'],
                    hash))
        self._conn.commit()

    ## Read cache entry for URL
    def read(self, url):
        hash = md5.new(url.strip('&fmt=18')).hexdigest()
        self._cur.execute('select ' \
            'cache_title,' \
            'cache_length,' \
            'cache_xurl,' \
            'cache_vid, ' \
            'cache_lowq ' \
            'from cache where cache_hash=?', (hash,))
        return self._cur.fetchone()

    ## Check if extraction URL has expired
    #
    # Some hosts have their extraction URLs expire after awhile.
    def has_xurl_expired(self, url):
        hosts = [('youtube.', 90),('dailymotion.', 2)]
        for (host, expires_min) in hosts: # Only these hosts req. check
            if url.find(host) != -1:
                hash = md5.new(url.strip('&fmt=18')).hexdigest()
                self._cur.execute('select ' \
                    'cache_time from cache where cache_hash=?', (hash,))
                row = self._cur.fetchone()
                if not row: return 0 # Not found -> not expired
                elapsed = time.time() - float(row[0])
                gmt = time.gmtime(elapsed)
                return (gmt[3]*60 + gmt[4]) >= expires_min
        return 0 # No such host -> not expired

    ## Updates extraction URL for an URL (hash)
    def update_expired_xurl(self, url, xurl):
        hash = md5.new(url.strip('&fmt=18')).hexdigest()
        self._cur.execute('update cache set cache_time=?, cache_xurl=? ' \
            'where cache_hash=?', (time.time(), xurl, hash))
        self._conn.commit()

    # Upgrades table if it appears to be outdated (e.g. missing columns)
    def _upgrade_if_needed(self):
        try:
            self._cur.execute('select * from cache limit 1')
            row = self._cur.fetchone()
            if not row: return
            if len(row) < 8:
                # Must be <= 0.4.10 database
                self._alter_table("'cache_lowq' integer not null default -1")
        except sqlite3.OperationalError, err:
            pass # Ignore quietly

    # Alter table by adding a new column
    def _alter_table(self, s):
        self._cur.execute('alter table cache add column %s' % s)
        self._conn.commit()
