diff --git a/README b/README index 017128d..2e8b63b 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ pyGTKtalog 1.0 -================== +============== pyGTKtalog is Linux/FreeBSD program for indexing CD/DVD or directories on filesystem. It is similar to gtktalog or @@ -75,6 +75,7 @@ There are still minor aims for versions 1.x to be done: - implement advanced search For version 2.0: +- Export/Import - Icon grid in files view - command line support: query, adding media to collection etc - internationalization @@ -82,7 +83,8 @@ For version 2.0: - user definied group of tags (represented by color in cloud tag) - hiding specified files - configurable, like dot prefixed, cfg and manualy selected - +- tests +- warning about existing image in media directory Removed: - filetypes handling (movies, images, archives, documents etc). Now it have common, unified external "plugin" system - simple text output from command @@ -103,17 +105,22 @@ Removed: NOTES ===== -Catalog file is tared and gziped sqlite database and directories with images and -thumbnails. If there are more images, the size of catalog file will grow. So be -carefull with adding big images in your catalog file! +Catalog file is plain sqlite database (optionally compressed with bzip2). All +images are stored in ~/.pygtktalog/images directory. Names for images are +generated sha512 hash from image file itself. There is small possibility for two +identical hash for different image files. However, no images are overwritten. +Thumbnail filename for each image is simply concatenation of image filename in +images directory and '_t' string. -There is also converter form old database to new. In fact no image are stored in -archive with katalog. All thumnails will be lost. All images without big image -will be lost. There ar serious changes with application design, and I decided, -that is better to keep media unpacked on disk, instead of pack it every time -with save and unpack with open methods. New design prevent from deleting eny -file from media directory (placed in ~/.pygtktalog/images). Functionality for -exporting images and corresponding db file is planned. +There is also converter from old database to new for internal use only. In +public release there will be no other formats so it will be useless, and +deleted. There are some issues with converting. All thumbnails will be lost. All +images without big image will be lost. There are serious changes with +application design, and I decided, that is better to keep media unpacked on +disk, instead of pack it every time with save and unpack with open methods. New +design prevent from deleting any file from media directory (placed in +~/.pygtktalog/images). Functionality for exporting images and corresponding db +file is planned. BUGS ==== diff --git a/pygtktalog.py b/pygtktalog.py index 8a62ce1..2cd92f9 100644 --- a/pygtktalog.py +++ b/pygtktalog.py @@ -64,13 +64,17 @@ def check_requirements(): pass try: - from pysqlite2 import dbapi2 as sqlite + import sqlite3 as sqlite except ImportError: - print "pyGTKtalog uses SQLite DB.\nYou'll need to get it and the", - print "python bindings as well.", - print "http://www.sqlite.org" - print "http://initd.org/tracker/pysqlite" - sys.exit(1) + try: + from pysqlite2 import dbapi2 as sqlite + except ImportError: + print "pyGTKtalog uses SQLite DB.\nYou'll need to get it and the", + print "python bindings as well.", + print "http://www.sqlite.org" + print "http://initd.org/tracker/pysqlite" + print "Alternatively install python 2.5 or higher" + sys.exit(1) if conf.confd['exportxls']: try: diff --git a/resources/glade/main.glade b/resources/glade/main.glade index 0e8b74f..e2b0af2 100644 --- a/resources/glade/main.glade +++ b/resources/glade/main.glade @@ -62,6 +62,27 @@ True + + + True + Import + True + + + + + + True + Export + True + + + + + + True + + True diff --git a/src/ctrls/c_main.py b/src/ctrls/c_main.py index 874831a..bc51fed 100644 --- a/src/ctrls/c_main.py +++ b/src/ctrls/c_main.py @@ -22,9 +22,9 @@ # ------------------------------------------------------------------------- -__version__ = "1.0 RC2" -LICENCE = open('LICENCE').read() - +__version__ = "1.0.1" +#LICENCE = open('LICENCE').read() +LICENCE = '' import os.path from os import popen from utils import deviceHelper @@ -871,6 +871,25 @@ class MainController(Controller): self.view['discs'].expand_all() return + def on_export_activate(self, menu_item): + """export db file and coressponding images to tar.bz2 archive""" + dialog = Dialogs.ChooseFilename(None, "Choose export file") + filepath = dialog.run() + + if not filepath: + return + + list_of_paths = self.view['images'].get_selected_items() + model = self.view['images'].get_model() + + count = 0 + + if len(list_of_paths) == 0: + # no picture was selected. default to save all of them + for image in model: + if self.model.save_image(image[0], filepath): + count += 1 + def on_collapse_all1_activate(self, menu_item): self.view['discs'].collapse_all() return @@ -1579,7 +1598,10 @@ class MainController(Controller): buf.insert_with_tags(buf.get_end_iter(), "Filename: ", tag) buf.insert(buf.get_end_iter(), set['filename'] + "\n") buf.insert_with_tags(buf.get_end_iter(), "Date: ", tag) - buf.insert(buf.get_end_iter(), str(set['fileinfo']['date']) + "\n") + try: + buf.insert(buf.get_end_iter(), str(set['fileinfo']['date']) + "\n") + except: + import ipdb; ipdb.set_trace() buf.insert_with_tags(buf.get_end_iter(), "Size: ", tag) buf.insert(buf.get_end_iter(), str(set['fileinfo']['size']) + "\n") diff --git a/src/utils/img.py b/src/utils/img.py index 543910c..81f31cc 100644 --- a/src/utils/img.py +++ b/src/utils/img.py @@ -23,11 +23,9 @@ # ------------------------------------------------------------------------- from shutil import copy -from os import path, mkdir +from os import path from hashlib import sha512 -from tempfile import mkstemp -from utils import EXIF import Image class Img(object): @@ -46,14 +44,15 @@ class Img(object): """Save image and asociated thumbnail into specific directory structure returns filename for image""" - + image_filename = path.join(self.base, self.sha512) thumbnail = path.join(self.base, self.sha512 + "_t") - + # check wheter image already exists if path.exists(image_filename) and path.exists(thumbnail): if __debug__: - print "image", self.filename, "with hash", self.sha512, "already exist" + print "image", self.filename, "with hash", + print self.sha512, "already exist" return self.sha512 if not path.exists(thumbnail): diff --git a/src/views/v_dialogs.py b/src/views/v_dialogs.py index 54f1817..1c090fb 100644 --- a/src/views/v_dialogs.py +++ b/src/views/v_dialogs.py @@ -45,10 +45,11 @@ class Qst(object): def run(self): retval = self.dialog.run() - self.dialog.destroy() + retval = False if retval == gtk.RESPONSE_OK: - return True - return False + retval = True + self.dialog.destroy() + return retval class Inf(object): """Show simple dialog for notices""" @@ -139,10 +140,11 @@ class InputDiskLabel(object): entry = gladexml.get_widget("volname") entry.set_text(self.label) result = dialog.run() - dialog.destroy() + res = None if result == gtk.RESPONSE_OK: - return entry.get_text() - return None + res = entry.get_text() + dialog.destroy() + return res class InputNewName(object): """Sepcific dialog for quering user for a disc label""" @@ -158,10 +160,11 @@ class InputNewName(object): entry = gladexml.get_widget("name") entry.set_text(self.name) result = dialog.run() - dialog.destroy() + res = None if result == gtk.RESPONSE_OK: - return entry.get_text() - return None + res = entry.get_text() + dialog.destroy() + return res class PointDirectoryToAdd(object): """Sepcific dialog for quering user for selecting directory to add""" @@ -206,7 +209,7 @@ class PointDirectoryToAdd(object): ch = True result = dialog.run() while ch: - if result == gtk.RESPONSE_OK and (self.volname.get_text()=='' or \ + if result == gtk.RESPONSE_OK and (self.volname.get_text() == '' or \ self.directory.get_text() == ''): a = Err("Error - pyGTKtalog", "There are fields needed to be filled.", @@ -215,11 +218,15 @@ class PointDirectoryToAdd(object): result = dialog.run() else: ch = False - dialog.destroy() + + volname = self.volname.get_text() + directory = self.directory.get_text() + + res = (None,None) if result == gtk.RESPONSE_OK: - return self.volname.get_text(),self.directory.get_text() - else: - return None,None + res = (volname, directory) + dialog.destroy() + return res class SelectDirectory(object): """Sepcific dialog for quering user for selecting directory to add""" @@ -257,15 +264,15 @@ class SelectDirectory(object): dialog.destroy() return retval -class ChooseDBFilename(object): - """Sepcific dialog for quering user for selecting filename for database""" +class ChooseFilename(object): + """Dialog for quering user for selecting filename""" URI=None - def __init__(self, path=None): + def __init__(self, path=None, title=''): self.path = path self.dialog = gtk.FileChooserDialog( - title="Save catalog as...", + title="", action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=( gtk.STOCK_CANCEL, @@ -276,7 +283,44 @@ class ChooseDBFilename(object): self.dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE) self.dialog.set_default_response(gtk.RESPONSE_OK) self.dialog.set_do_overwrite_confirmation(True) - self.dialog.set_title('Save catalog to file...') + self.dialog.set_title(title) + + f = gtk.FileFilter() + f.set_name("Catalog files") + f.add_pattern("*.sqlite") + f.add_pattern("*.sqlite.bz2") + self.dialog.add_filter(f) + f = gtk.FileFilter() + f.set_name("All files") + f.add_pattern("*.*") + self.dialog.add_filter(f) + + def run(self): + if self.URI: + self.dialog.set_current_folder_uri(self.URI) + elif self.path and os.path.exists(self.path): + self.path = "file://"+os.path.abspath(self.path) + self.dialog.set_current_folder_uri(self.path) + + response = self.dialog.run() + if response == gtk.RESPONSE_OK: + filename = self.dialog.get_filename() + self.__class__.URI = self.dialog.get_current_folder_uri() + self.dialog.destroy() + return filename + else: + self.dialog.destroy() + return None + pass + +class ChooseDBFilename(ChooseFilename): + """Sepcific dialog for quering user for selecting filename for database""" + + URI=None + + def __init__(self, path=None): + ChooseFilename.__init__(self) + self.dialog.set_title('Save catalog as...') f = gtk.FileFilter() f.set_name("Catalog files") @@ -298,11 +342,6 @@ class ChooseDBFilename(object): response = self.dialog.run() if response == gtk.RESPONSE_OK: filename = self.dialog.get_filename() - print filename, ' do ', - if filename[-11:].lower() != '.sqlite.bz2' and \ - filename[-7:].lower() != '.sqlite': - filename = filename + '.sqlite.bz2' - print filename self.__class__.URI = self.dialog.get_current_folder_uri() self.dialog.destroy() return filename @@ -488,10 +527,11 @@ class StatsDialog(object): entry.set_text(str(self.values['size'])) result = dialog.run() - dialog.destroy() + retval = None if result == gtk.RESPONSE_OK: - return entry.get_text() - return None + retval = entry.get_text() + dialog.destroy() + return retval class TagsDialog(object): """Sepcific dialog for display stats""" @@ -507,10 +547,11 @@ class TagsDialog(object): result = dialog.run() - dialog.destroy() + retval = None if result == gtk.RESPONSE_OK: - return entry.get_text() - return None + retval = entry.get_text() + dialog.destroy() + return retval class TagsRemoveDialog(object): """Sepcific dialog for display stats""" @@ -572,14 +613,15 @@ class TagsRemoveDialog(object): result = dialog.run() - dialog.destroy() + retval = (None, None) if result == gtk.RESPONSE_OK: ids = [] for i in model: if i[2]: ids.append(i[0]) - return "ok", ids - return None, None + retval = ("ok", ids) + dialog.destroy() + return retval class EditDialog(object): """Sepcific dialog for display stats"""