# Cache.py
# $Id: Cache.py,v 1.14 2001/07/14 22:50:28 s2mdalle Exp $
# Written by David Allen <mda@idatar.com>
# Released under the terms of the GNU General Public License
#
# Handles cache-file related operations.
#
#  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., 675 Mass Ave, Cambridge, MA 02139, USA.
############################################################################
import os
import utils
import string
from gopher           import *
from Tkinter          import *
import GopherResponse
import Pmw
import Options

CacheException = "Error"

class Cache:
    verbose = None
    
    def __init__(self, *args):
        self._d = None
        return None

    def getCacheDirectory(self):
        try:
            dir = Options.program_options.getOption('cache_directory')
            return os.path.abspath(dir)
        except:
            return os.path.abspath("cache")

    def getCachePrefix(self):
        try:
            dir = Options.program_options.getOption('cache_prefix')
            return os.path.abspath(dir)
        except:
            return os.path.abspath("cache%scache" % os.sep)

    def getCacheStats(self):
        cdir = self.getCacheDirectory()
        
        [filecount, totalBytes, dircount] = utils.summarize_directory(cdir)

        kbcount = float(totalBytes)/float(1024)
        mbcount = kbcount/float(1024)

        kbcount = "%0.2fKB" % kbcount
        mbcount = "%0.2fMB" % mbcount

        datasize = "There is %s (%s) of data" % (kbcount, mbcount)
        fdcount  = "in %s files (%s directories total)" % (filecount, dircount)
        closer   = "underneath %s" % cdir

        return "%s\n%s\n%s" % (datasize, fdcount, closer)

    def emptyCache(self, parentTk):
        pref = self.getCachePrefix()
        
        self.dialog = Pmw.Dialog(parent=parentTk,
                                 title="Really delete cache?",
                                 buttons=('OK', 'Cancel'),
                                 defaultbutton='Cancel',
                                 command=self.__confirmDelete)
        
        fr = Frame(self.dialog.interior())
        fr.pack(side='top', fill='both', expand=1)

        lab1 = Label(fr, text="Are you sure you want to empty the cache?")
        lab2 = Label(fr, text="WARNING:  THIS WILL DELETE ALL FILES AND")
        lab3 = Label(fr, text="ALL SUBDIRECTORIES IN YOUR CACHE DIRECTORY,")
        lab4 = Label(fr, text="%s" % pref)
        lab5 = Label(fr, text=" ")
        lab6 = Label(fr, text="Continue?")

        lab1.grid(row=1, col=0, columnspan=5)
        lab2.grid(row=2, col=0, columnspan=5)
        lab3.grid(row=3, col=0, columnspan=5)
        lab4.grid(row=4, col=0, columnspan=5)
        lab5.grid(row=5, col=0, columnspan=5)
        lab6.grid(row=6, col=0, columnspan=5)
                                                
        # self.dialog.activate()

    def deleteCacheNoPrompt(self, *args):
        """Delete the cache without asking the user.  Do not do this unless
        you know what you're doing."""
        return self.__deleteCache()

    def __confirmDelete(self, button):
        if button != 'OK':
            self.dialog.destroy()
            return None

        self.dialog.destroy()   # Destroy the dialog anyway
        self.__deleteCache()    # Clean the files out
        return None
        
    def __deleteCache(self):
        pref = self.getCachePrefix()

        if not utils.dir_exists(pref):
            raise CacheException, "Cache prefix %s doesn't exist." % pref
                        
        cache_directories = os.listdir(pref)

        # I've been told that there's a shell utility module that does this
        # probably safer and better, but I'm not sure where it is, or whether
        # or not it's portable to crappy no-name 32 bit operating systems
        # out of Redmond, Washington.
        
        for item in cache_directories:
            item = "%s%s%s" % (pref, os.sep, item)
            if os.path.isdir(item):
                print "Recursively deleting \"%s\"" % item
                utils.recursive_delete(item)
            else:
                print "Eh?  \"%s\" isn't a directory.  That's odd..." % item

    def isInCache(self, resource):
        """Takes a resource, and returns true if the resource seems to have
        an available cache file associated with it, and None otherwise."""

        pref     = self.getCachePrefix()
        filename = resource.toCacheFilename()
        
        if pref[len(pref)-1] != os.sep and filename[0] != os.sep:
            # When joining together, separate paths with os.sep
            pref = "%s%s" % (pref, os.sep)

        # Now join together, and they will be properly joined.
        filename = pref + filename

        try:
            info = os.stat(os.path.abspath(filename))
            return [os.path.abspath(filename), info[6]]
        except OSError:
            return None
        return None
        
    def uncache(self, resource):
        """Takes a resource, and returns either None if the given resource
        is not cached on disk, or it returns a GopherResponse corresponding
        to what would have been gotten if it had been fetched from the
        server."""

        pref = self.getCachePrefix()
        file = resource.toCacheFilename()

        if pref[len(pref)-1] != os.sep and file[0] != os.sep:
            # When joining together, separate paths with os.sep
            pref = "%s%s" % (pref, os.sep)
            
        filename = pref + file
        
        try:
            # See if the file exists...
            tuple = os.stat(filename)
            if self.verbose:
                print "File %s of size %d exists." % (filename, tuple[6])
        except OSError:
            # The file doesn't exist, we can't uncache it.
            return None

        print "===> Uncaching \"%s\"" % filename
        resp = GopherResponse.GopherResponse()
        resp.setType(resource.getTypeCode())
        resp.setHost(resource.getHost())
        resp.setPort(resource.getPort())
        resp.setLocator(resource.getLocator())
        resp.setName(resource.getName())
        
        try:
            fp = open(filename, "r")

            # Consider reworking this somehow.  Slurp the entire file into
            # buffer.
            buffer = fp.read()
            fp.close()

            try:
                resp.parseResponse(buffer)
                resp.setData(None)
                # print "Loaded cache is a directory entry."
            except:
                # print "Loaded cache is not a directory."
                resp.setData(buffer)
                
            # Got it!  Loaded from cache anyway...
            # print "UNCACHE found data for use."
            return resp       
        except IOError, errstr:
            raise CacheException, "Couldn't read data on\n%s:\n%s" % (filename,
                                                                      errstr)

        # We failed.  Oh well...
        return None
    
    def cache(self, resp, resource):
        """Takes a GopherResponse and a GopherResource.  Saves the content of
        the response to disk, and returns the filename saved to."""

        if resource.isAskType():
            # Don't cache ASK blocks.  This is because if you do, the program
            # will interpret it as data, and put the question structure inside
            # a text box.   Plus, since these may be dynamic, caching could
            # lead to missing out on things.
            raise CacheException, "Do not cache AskTypes.  Not a good idea."
        
        basedir      = self.getCachePrefix()
        basefilename = resource.toCacheFilename()

        # Problem - basedir is our base directory, but basefilename contains
        # trailing filename info that shouldn't be part of the directories
        # we're creating.
        filenamecopy = basefilename[:]
        ind = string.rfind(filenamecopy, os.sep)
        
        # Chop off extra info so "/home/x/foobar" becomes "/home/x/foobar"
        # this is because make_directories will otherwise create foobar as
        # a directory when it's actually a filename
        filenamecopy = filenamecopy[0:ind]     

        # Create the directory structure where necessary
        utils.make_directories(filenamecopy, basedir)

        basedirlastchar = basedir[len(basedir)-1]
        if basedirlastchar == os.sep:
            filename = "%s%s" % (basedir, basefilename)
        else:
            filename = "%s%s%s" % (basedir, os.sep, basefilename)

        # print "Cache: caching data to \"%s\"" % filename
        
        try:
            fp = open(filename, "w")

            if resp.getData() == None:    # This is a directory entry.
                response_lines = resp.getResponses()
                # Each response line is a GopherResource
                # write them as if it was a file served by the gopher server.
                # that way it can be easily reparsed when loaded from the
                # cache.
                for response_line in response_lines:
                    fp.write(response_line.toProtocolString())

                # write the string terminator.  This isn't really needed
                # since it isn't data, but it helps fool our other objects
                # into thinking that it's dealing with data off of a socket
                # instead of data from a file.  So do it.
                fp.write("\r\n.\r\n") 
            else:
                fp.write(resp.getData())

            fp.flush()
            fp.close()
        except IOError, errstr:
            # Some error writing data to the file.  Bummer.
            raise CacheException, "Couldn't write to\n%s:\n%s" % (filename,
                                                                  errstr)
        # Successfully wrote the data - return the filename that was used
        # to save the data into.  (Absolute path)
        return os.path.abspath(filename)
        

