# GutenPy - catalog.py
# Copyright (C) 2006 Lee Bigelow <ligelowbee@yahoo.com> 
# 
# 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

import os, sys, cPickle, re, urllib
import gtk, pango
from rdfparse import Ebook
from charset_widget import *
(ID_COL, AUTHOR_COL, TITLE_COL, SUBJ_COL,
 LANG_COL, MDATE_COL, FILENAME_COL, SAVENAME_COL)=range(8)

class CatalogFilterBox(gtk.HBox):
    def __init__(self, parent):
        gtk.HBox.__init__(self, False, 2)
        vbox = gtk.VBox(False, 2)
        tips = gtk.Tooltips()
        tips.enable()

        prefs = parent.parent_app.prefs
        parent.remember_author = gtk.CheckButton('Author Filter:')
        parent.remember_author.connect('toggled', parent.remember_toggle,
                                       'remember_author')
        parent.remember_author.set_active(prefs.remember_author)
        tips.set_tip(parent.remember_author,
                     'Check to remember for next session') 
        parent.author_entry = gtk.Entry()
        parent.author_entry.set_text(parent.author_filter)
        parent.author_entry.connect('activate', parent.apply_filter)
        vbox.pack_start(parent.remember_author, False)
        vbox.pack_start(parent.author_entry)
        self.pack_start(vbox)

        vbox = gtk.VBox(False, 2)
        parent.remember_title = gtk.CheckButton('Title Filter:')
        parent.remember_title.connect('toggled', parent.remember_toggle,
                                      'remember_title')
        parent.remember_title.set_active(prefs.remember_title)
        tips.set_tip(parent.remember_title,
                     'Check to remember for next session') 
        parent.title_entry = gtk.Entry()
        parent.title_entry.set_text(parent.title_filter)
        parent.title_entry.connect('activate', parent.apply_filter)
        vbox.pack_start(parent.remember_title, False)
        vbox.pack_start(parent.title_entry)
        self.pack_start(vbox)
        
        vbox = gtk.VBox(False, 2)
        parent.remember_subject = gtk.CheckButton('Subject Filter:')
        parent.remember_subject.connect('toggled', parent.remember_toggle,
                                        'remember_subject')
        parent.remember_subject.set_active(prefs.remember_subject)
        tips.set_tip(parent.remember_subject,
                     'Check to remember for next session') 
        parent.subject_entry = gtk.Entry()
        parent.subject_entry.set_text(parent.subject_filter)
        parent.subject_entry.connect('activate', parent.apply_filter)
        vbox.pack_start(parent.remember_subject, False)
        vbox.pack_start(parent.subject_entry)
        self.pack_start(vbox)
        
        vbox = gtk.VBox(False, 2)
        parent.remember_lang = gtk.CheckButton('Lang Filter:')
        parent.remember_lang.connect('toggled', parent.remember_toggle,
                                     'remember_lang')
        parent.remember_lang.set_active(prefs.remember_lang)
        tips.set_tip(parent.remember_lang,
                     'Check to remember for next session') 
        parent.lang_entry = gtk.Entry()
        parent.lang_entry.set_width_chars(4)
        parent.lang_entry.set_text(parent.lang_filter)
        parent.lang_entry.connect('activate', parent.apply_filter)
        vbox.pack_start(parent.remember_lang, False)
        vbox.pack_start(parent.lang_entry)
        self.pack_start(vbox)

        button = gtk.Button(stock=gtk.STOCK_APPLY)
        button.connect('clicked', parent.apply_filter)
        self.pack_start(button, False, False, 1)


class CatalogTreeView(gtk.TreeView):
    def __init__(self, model=None):
        gtk.TreeView.__init__(self, model)
        col_names = ['Id','Author','Title','Subject', 'Lang', 'MDate']
        for name in col_names:
            textcell = gtk.CellRendererText()
            tvcolumn = gtk.TreeViewColumn(name,textcell,
                                          text=col_names.index(name))
            if name in ['Title','Author', 'Subject']:
                textcell.set_property('ellipsize', pango.ELLIPSIZE_END)
                tvcolumn.set_expand(True)
                tvcolumn.set_resizable(True)
            if name == 'Title':
                textcell.set_property('width-chars', 25 )
            elif name == 'Subject':
                textcell.set_property('width-chars', 5)
            elif name == 'Lang':
                textcell.set_property('width-chars', 2)
            tvcolumn.set_sort_column_id(col_names.index(name))
            self.append_column(tvcolumn)
        self.set_search_column(AUTHOR_COL)
        sel = self.get_selection()
        sel.set_mode(gtk.SELECTION_MULTIPLE)


class CatalogListStore(gtk.ListStore):
    def __init__(self, catalog=None):
        #bookid, author, title, subject, language, mdate, filename, savename
        gtk.ListStore.__init__(self, str, str, str, str,
                                     str, str, str, str)
        if catalog:
            book = catalog.pop(0)
            while book:
                self.append([book.bookid, book.author, book.title, book.subj,
                             book.lang, book.mdate, book.filename, ''])
                try: book = catalog.pop(0)
                except: book = None


class CatalogButtonBox(gtk.HButtonBox):
    def __init__(self, parent):
        gtk.HButtonBox.__init__(self)
        self.set_layout(gtk.BUTTONBOX_SPREAD)
        
        button = gtk.Button('Add _To Booklist')
        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_BUTTON)
        button.set_image(image)
        button.connect('clicked', parent.add_to_booklist)
        self.pack_start(button, False, False, 0)
        
        button = gtk.Button('Remove _From Booklist')
        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_BUTTON)
        button.set_image(image)
        button.connect('clicked', parent.delete_selected_from_booklist)
        self.pack_start(button, False, False, 3)
         
        button = gtk.Button('_Clear Booklist')
        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON)
        button.set_image(image)
        button.connect('clicked', parent.clear_booklist)
        self.pack_start(button, False, False, 3)
         
        self.show_all()

        
class DownloadDialog(gtk.Dialog):
    def __init__(self, parent):
        gtk.Dialog.__init__(self, "Download Book", parent,
                            gtk.DIALOG_DESTROY_WITH_PARENT,
                            (gtk.STOCK_OK, gtk.RESPONSE_OK,
                             gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
        self.set_default_response(gtk.RESPONSE_OK)

        self.book_info = gtk.Label()
        self.vbox.pack_start(self.book_info)
        self.book_info.set_selectable(True)
        
        dir_hbox = gtk.HBox()
        self.vbox.pack_start(dir_hbox)

        label = gtk.Label("Download to Directory:")
        dir_hbox.pack_start(label)

        self.dirbutton = gtk.FileChooserButton('Download Destination')
        dir_hbox.pack_start(self.dirbutton)
        self.dirbutton.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)

        self.entry = gtk.Entry()
        self.entry.set_activates_default(True)
        self.vbox.pack_start(self.entry)

        self.vbox.show_all()

        
class CatalogDialog(gtk.Dialog):
    def __init__(self, parent, catalog):
        gtk.Dialog.__init__(self, "Gutenberg Catalog")
        self.connect('delete_event', self.quit)
        self.set_action_buttons()
        self.set_default_size(600,550)
        self.parent_app = parent
        self.author_filter = parent.prefs.author_filter
        self.title_filter = parent.prefs.title_filter
        self.subject_filter = parent.prefs.subject_filter
        self.lang_filter = parent.prefs.lang_filter
        
        # put paned window in dialog vbox
        vpane = gtk.VPaned()
        self.vbox.pack_start(vpane)

        # Fill in the top pane with vbox containing filters and
        # catalog tree
        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_IN)
        topvbox = gtk.VBox()
        frame.add(topvbox)
        vpane.pack1(frame, False, False)
        hbox = CatalogFilterBox(self)
        topvbox.pack_start(hbox, False, False, 1)

        scrollw =  gtk.ScrolledWindow()
        scrollw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        topvbox.pack_start(scrollw)
        self.modelfilter = CatalogListStore(catalog).filter_new()
        self.modelfilter.set_visible_func(self.model_filter_func)
        self.treeview = CatalogTreeView(gtk.TreeModelSort(self.modelfilter))
        self.treeview.get_column(AUTHOR_COL).set_title(
            "Author (%s)" % self.author_filter)
        self.treeview.get_column(TITLE_COL).set_title(
            "Title (%s)" % self.title_filter)
        self.treeview.get_column(SUBJ_COL).set_title(
            "Subject (%s)" % self.subject_filter)
        self.treeview.connect("row_activated", self.catalog_row_activated)
        scrollw.add(self.treeview)

        # Fill in bottom box vwith selection buttons, label
        # and booklist tree
        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_IN)
        bottomvbox = gtk.VBox()
        frame.add(bottomvbox)
        vpane.pack2(frame, False, True)

        buttonbox = CatalogButtonBox(self)
        bottomvbox.pack_start(buttonbox, False, False, 2)

        hbox = gtk.HBox()
        bottomvbox.pack_start(hbox, False, False, 0)
        label = gtk.Label("Booklist for downloading:")
        hbox.pack_start(label, False, False, 0)

        
        scrollw = gtk.ScrolledWindow()
        scrollw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        bottomvbox.pack_start(scrollw, True,True, 0)
        self.sel_liststore = self.load_booklist()
        self.sel_treeview = CatalogTreeView(self.sel_liststore)
        scrollw.add(self.sel_treeview)

        self.vbox.show_all()

    def catalog_row_activated(self, treeview, path=None, column=None):
        self.add_to_booklist()

    def remember_toggle(self, toggle, attr):
        prefs = self.parent_app.prefs
        setattr(prefs, attr, toggle.get_active())
        
    def set_action_buttons(self):
        button = gtk.Button('_Download Booklist')
        button.connect('clicked', self.download_booklist)
        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_OK, gtk.ICON_SIZE_BUTTON)
        button.set_image(image)
        self.action_area.pack_start(button)

        button = gtk.Button('_Save Booklist')
        button.connect('clicked', self.save_booklist)
        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_SAVE, gtk.ICON_SIZE_BUTTON)
        button.set_image(image)
        self.action_area.pack_start(button)

        button = gtk.Button(stock=gtk.STOCK_QUIT)
        button.connect('clicked', self.quit)
        self.action_area.pack_start(button)

    def download_booklist(self, widget=None, user_data=None):
        model = self.sel_liststore
        iter = model.get_iter_first()
        if not iter: return
        s_list = []
        while iter:
            self.booklist_set_savename(model, iter)
            iter = model.iter_next(iter)
        if self.parent_app:
            mirror = self.parent_app.prefs.mirror
            bookdir = self.parent_app.prefs.bookdir
        else:
            mirror = "http://www.gutenberg.org/dirs"
            bookdir = "/tmp"
        error_list = []
        success_list = []
        iter = model.get_iter_first()
        while iter:
            fname, dest = model.get(iter, FILENAME_COL, SAVENAME_COL)
            if dest:
                book_url = '%s/%s' % (mirror, fname)
                success = self.get_book(book_url, dest)
                title = model.get_value(iter, TITLE_COL)
                if success:
                    success_list.append(title)
                else:
                    error_list.append(title)
            iter = model.iter_next(iter)
        if success_list: success_list = '\n\t'.join(success_list)
        else: success_list = "None"
        if error_list: error_list = '\n\t'.join(error_list)
        else: error_list = "None"
        dialog = gtk.MessageDialog(
            parent=self,
            buttons=gtk.BUTTONS_OK,
            message_format="Downloading Complete.\n\n"
                           "Successful:\n\t%s\n\n"
                           "Errors:\n\t%s\n" % ( success_list, error_list) )
        dialog.show_all()
        dialog.connect('response', lambda w, d: w.destroy())
        
    def booklist_set_savename(self, model, iter):
        dialog = DownloadDialog(self)
        title, author = model.get(iter, TITLE_COL, AUTHOR_COL)
        dialog.book_info.set_text('Download Book\n\n'
                                  'Title:\t%s\nAuthor:\t%s\n\n'
                                  'As File:' % (title, author))
        fs_enc = sys.getfilesystemencoding()
        try:
            t = title.encode(fs_enc, 'strict')
            # windows is succeeding with a replace when it should fail so..
            if '?' in t: raise UnicodeError
        except:
            label = gtk.Label(
                "\n(Warning: title contains characters not in your\n"
                "filesystem's charset.  Filename '?' characters need to be\n" 
                "replaced with viable characters for charset '%s')\n" % fs_enc)
            dialog.vbox.pack_start(label, True, True, 5)
            label.show()
        title = title.encode(fs_enc, 'replace')
        title = unicode(title, fs_enc)
        dialog.entry.set_text('%s.zip' % title)
        dialog.dirbutton.set_current_folder(self.parent_app.prefs.bookdir)
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            destdir = dialog.dirbutton.get_filename()
            destdir, punt, enc = decode_filename_heuristically(destdir)
            if not enc:
                dialog.destroy()
                return
            savename = unicode(dialog.entry.get_text(), 'utf_8')
            if destdir and savename:
                savepath = os.path.join(destdir, savename)
                model.set(iter, SAVENAME_COL, savepath)
        dialog.destroy()
        
    def get_book(self, book_url, dest):
        dialog = gtk.MessageDialog(self,
            message_format="Downloading:\n\n%s\n\tto\n%s\n" % (
            book_url, dest))
        dialog.show_all()
        dest = dest.encode(sys.getfilesystemencoding(), 'replace')
        while gtk.events_pending():
            gtk.main_iteration()
        try:
            print "dest:" , dest
            filename, headers = urllib.urlretrieve(book_url, dest)
            dialog.destroy()
            return True
        except IOError, msg:
            print msg
            dialog.destroy()
            return False

    def save_booklist(self, widget=None, user_data=None):
        liststore = self.sel_liststore
        iter = liststore.get_iter_first()
        sel_list = []
        while iter:
            (bookid,author,title,subj,lang,mdate,filename)=liststore.get(
                iter, ID_COL, AUTHOR_COL, TITLE_COL, SUBJ_COL, LANG_COL,
                MDATE_COL, FILENAME_COL)
            sel_list.append([bookid, author, title, subj,
                             lang, mdate, filename])
            iter = liststore.iter_next(iter)
        cfgdir = self.parent_app.prefs.cfgdir
        sel_pickle = os.path.join(cfgdir, 'selection.pickle')
        try:
            f = open(sel_pickle, 'wb')
            cPickle.dump(sel_list, f, -1)
            f.close()
            dialog = gtk.MessageDialog(parent=self,
                         buttons=gtk.BUTTONS_OK,
                         message_format="Booklist for downloading saved.")
            dialog.run()
            dialog.destroy()
        except IOError, msg:
            print msg
            dialog = gtk.MessageDialog(parent=self,
                         buttons=gtk.BUTTONS_OK,
                         type=gtk.MESSAGE_ERROR,
                         message_format="Could not save booklist.")
            dialog.run()
            dialog.destroy()
        
    def load_booklist(self):
        liststore = CatalogListStore()
        cfgdir = self.parent_app.prefs.cfgdir
        sel_pickle = os.path.join(cfgdir, 'selection.pickle')
        if os.path.isfile(sel_pickle):
            try:
                f =  open(sel_pickle, 'rb')
                sel_list = cPickle.load(f)
                f.close()
            except:
                print "error creating selection"
                sel_list = []
            if sel_list:
                for book in sel_list:
                    liststore.append([book[0],book[1],book[2],book[3],
                                      book[4], book[5], book[6], ''])
        return liststore
    
    def add_to_booklist(self, button=None):
        selection = self.treeview.get_selection()
        if selection.count_selected_rows() < 1: return
        model, paths = selection.get_selected_rows()
        for path in paths:
            iter = model.get_iter(path)
            (bookid, author, title,
            subj, lang,
            mdate, fname) = model.get(iter, ID_COL,
                                      AUTHOR_COL, TITLE_COL,
                                      SUBJ_COL, LANG_COL,
                                      MDATE_COL, FILENAME_COL)
            if self.check_unique_addition(bookid):
                self.sel_liststore.append([bookid, author, title,
                                           subj, lang, mdate, fname,''])

    def check_unique_addition(self, bookid):
        model = self.sel_liststore
        if len(model) < 1: return True
        for i in range(len(model)):
            if bookid == model[i][ID_COL]:
                return False
        return True
    
    def delete_selected_from_booklist(self, button=None):
        selection = self.sel_treeview.get_selection()
        if selection.count_selected_rows() < 1: return
        model, paths = selection.get_selected_rows()
        rowref_list = []
        for path in paths:
            rowref_list.append(gtk.TreeRowReference(model, path))
        for rowref in rowref_list:
            path = rowref.get_path()
            if path:
                iter = model.get_iter(path)
                if iter: model.remove(iter)

    def clear_booklist(self, button=None):
        self.sel_liststore.clear()
        
    def apply_filter(self, widget=None):
        self.author_filter = self.author_entry.get_text()
        self.title_filter = self.title_entry.get_text()
        self.subject_filter = self.subject_entry.get_text()
        self.lang_filter = self.lang_entry.get_text()
        self.modelfilter.refilter()
        self.treeview.get_column(AUTHOR_COL).set_title("Author (%s)" %
                                                       self.author_filter)
        self.treeview.get_column(TITLE_COL).set_title("Title (%s)" %
                                                      self.title_filter)
        self.treeview.get_column(SUBJ_COL).set_title("Subject (%s)" %
                                                      self.subject_filter)
            
    def model_filter_func(self, model, iter, data=None):
        author, title, subj, lang = model.get(iter,AUTHOR_COL,TITLE_COL, SUBJ_COL, LANG_COL)
        a_found = self.found_filter(self.author_filter, author)
        t_found = self.found_filter(self.title_filter, title)
        s_found = self.found_filter(self.subject_filter, subj)
        l_found = self.found_filter(self.lang_filter, lang)

        if a_found and t_found and s_found and l_found:
            return True
        else:
            return False

    def found_filter(self, filter, string):
        if not filter: return True
        else:
            terms_found=0
            regex=re.compile(filter, re.I)
            if regex.search(string): return True
            else: return False

    def quit(self, widget=None, user_data=None):
        prefs = self.parent_app.prefs
        if prefs.remember_author:
            prefs.author_filter = self.author_filter
        else:
            prefs.author_filter = ''

        if prefs.remember_title:
            prefs.title_filter = self.title_filter
        else:
            prefs.title_filter = ''

        if prefs.remember_subject:
            prefs.subject_filter = self.subject_filter
        else:
            prefs.subject_filter = ''

        if prefs.remember_lang:
            prefs.lang_filter = self.lang_filter
        else:
            prefs.lang_filter = ''

        self.parent_app.catalog_dialog = None
        self.destroy()
        
