1
0
mirror of https://github.com/gryf/pygtktalog.git synced 2025-12-17 11:30:19 +01:00

* Finally, version 1.0.

This commit is contained in:
2008-11-02 19:48:41 +00:00
parent e9edb602e3
commit 92016fe5c1
6 changed files with 390 additions and 315 deletions

10
README
View File

@@ -1,4 +1,4 @@
pyGTKtalog 1.0 RC3 pyGTKtalog 1.0
================== ==================
pyGTKtalog is Linux/FreeBSD program for indexing CD/DVD or directories on pyGTKtalog is Linux/FreeBSD program for indexing CD/DVD or directories on
@@ -107,6 +107,14 @@ 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 thumbnails. If there are more images, the size of catalog file will grow. So be
carefull with adding big images in your catalog file! carefull with adding big images in your catalog file!
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.
BUGS BUGS
==== ====

View File

@@ -179,17 +179,6 @@
<signal name="activate" handler="on_del_all_images_activate"/> <signal name="activate" handler="on_del_all_images_activate"/>
</widget> </widget>
</child> </child>
<child>
<widget class="GtkMenuItem" id="del_all_images_thumb">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="tooltip" translatable="yes">Deletes all images from files in current colection.
Thumbnails from image tabs will be keep.</property>
<property name="label" translatable="yes">Delete all images without thumbnals</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_del_all_images_thumb_activate"/>
</widget>
</child>
<child> <child>
<widget class="GtkMenuItem" id="del_all_thumb"> <widget class="GtkMenuItem" id="del_all_thumb">
<property name="visible">True</property> <property name="visible">True</property>
@@ -199,6 +188,14 @@ Thumbnails from image tabs will be keep.</property>
<signal name="activate" handler="on_del_all_thumb_activate"/> <signal name="activate" handler="on_del_all_thumb_activate"/>
</widget> </widget>
</child> </child>
<child>
<widget class="GtkMenuItem" id="save_all_img">
<property name="visible">True</property>
<property name="label" translatable="yes">Save all images...</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_save_all_img_activate"/>
</widget>
</child>
<child> <child>
<widget class="GtkSeparatorMenuItem" id="separator12"> <widget class="GtkSeparatorMenuItem" id="separator12">
<property name="visible">True</property> <property name="visible">True</property>
@@ -1007,20 +1004,18 @@ thumbnail from first image will be generated.</property>
<child> <child>
<widget class="GtkMenuItem" id="img_delete"> <widget class="GtkMenuItem" id="img_delete">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">_Delete images</property> <property name="label" translatable="yes">_Delete images</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<signal name="activate" handler="on_img_delete_activate"/> <signal name="activate" handler="on_img_delete_activate"/>
</widget> </widget>
</child> </child>
<child> <child>
<widget class="GtkMenuItem" id="img_delete2"> <widget class="GtkMenuItem" id="img_thumbset">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="tooltip" translatable="yes">Delete selected images, but leave thumbnails</property> <property name="label" translatable="yes">Set as _thumbnail</property>
<property name="label" translatable="yes">De_lete images (keep thumbs)</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<signal name="activate" handler="on_img_delete2_activate"/> <signal name="activate" handler="on_img_thumbset_activate"/>
</widget> </widget>
</child> </child>
<child> <child>

View File

@@ -49,7 +49,7 @@ class MainController(Controller):
widgets_all = ('tag_path_box', 'hpaned1', widgets_all = ('tag_path_box', 'hpaned1',
'file1', 'edit1', 'view1', 'help1', 'file1', 'edit1', 'view1', 'help1',
'add_cd', 'add_directory1', 'del_all_images', 'add_cd', 'add_directory1', 'del_all_images',
'del_all_images_thumb', 'del_all_thumb', 'stat1', 'del_all_thumb', 'stat1',
'tb_new','tb_open', 'tb_save', 'tb_addcd', 'tb_adddir', 'tb_new','tb_open', 'tb_save', 'tb_addcd', 'tb_adddir',
'tb_find', 'tb_quit') 'tb_find', 'tb_quit')
widgets_cancel = ('cancel','cancel1') widgets_cancel = ('cancel','cancel1')
@@ -120,7 +120,7 @@ class MainController(Controller):
self.view['tag_cloud_textview'].connect("populate-popup", self.view['tag_cloud_textview'].connect("populate-popup",
self.on_tag_cloud_textview_popup) self.on_tag_cloud_textview_popup)
# in case model has opened file, register tags # in case model has opened file, register tags
if self.model.internal_dirname: if self.model.db_tmp_path:
self.__tag_cloud() self.__tag_cloud()
else: else:
self.model.new() self.model.new()
@@ -345,7 +345,7 @@ class MainController(Controller):
ImageView(img) ImageView(img)
else: else:
Dialogs.Inf("Image view", "No Image", Dialogs.Inf("Image view", "No Image",
"This item have no real image, only thumbnail.") "Image file does not exist.")
def on_rename1_activate(self, widget): def on_rename1_activate(self, widget):
model, iter = self.view['discs'].get_selection().get_selected() model, iter = self.view['discs'].get_selection().get_selected()
@@ -749,7 +749,7 @@ class MainController(Controller):
for path in list_of_paths: for path in list_of_paths:
iterator = model.get_iter(path) iterator = model.get_iter(path)
ids.append(model.get_value(iterator, 0)) ids.append(model.get_value(iterator, 0))
for fid in ids: for fid in ids:
self.model.delete_image(fid) self.model.delete_image(fid)
@@ -805,37 +805,23 @@ class MainController(Controller):
description) description)
return return
def on_img_delete2_activate(self, menu_item): def on_img_thumbset_activate(self, menu_item):
"""remove images, but keep thumbnails""" """set selected image as thumbnail"""
list_of_paths = self.view['images'].get_selected_items() list_of_paths = self.view['images'].get_selected_items()
if not list_of_paths: if not list_of_paths:
Dialogs.Inf("Delete images", "No images selected", Dialogs.Inf("Set thumbnail", "No image selected",
"You have to select at least one image to delete.") "You have to select one image to set as thumbnail.")
return
if len(list_of_paths) >1:
Dialogs.Inf("Set thumbnail", "To many images selected",
"You have to select one image to set as thumbnail.")
return return
if self.model.config.confd['delwarn']:
description = 'Selected images will be permanently removed from '
description += 'catalog,\nthumbnails will be keeped.'
obj = Dialogs.Qst('Delete images', 'Delete selected images?',
description)
if not obj.run():
return
model = self.view['images'].get_model() model = self.view['images'].get_model()
for path in list_of_paths: iter = model.get_iter(list_of_paths[0])
iter = model.get_iter(path) id = model.get_value(iter, 0)
id = model.get_value(iter, 0) self.model.set_image_as_thumbnail(id)
self.model.delete_images_wth_thumbs(id)
try:
path, column = self.view['files'].get_cursor()
model = self.view['files'].get_model()
iter = model.get_iter(path)
id = model.get_value(iter, 0)
self.__get_item_info(id)
except:
pass
self.model.unsaved_project = True self.model.unsaved_project = True
self.__set_title(filepath=self.model.filename, modified=True) self.__set_title(filepath=self.model.filename, modified=True)
@@ -1049,7 +1035,7 @@ class MainController(Controller):
ids = self.__get_tv_selection_ids(self.view['files']) ids = self.__get_tv_selection_ids(self.view['files'])
for id in ids: for id in ids:
self.model.add_tags(id, tags) self.model.add_tags(id, tags)
self.__tag_cloud() self.__tag_cloud()
self.model.unsaved_project = True self.model.unsaved_project = True
self.__set_title(filepath=self.model.filename, modified=True) self.__set_title(filepath=self.model.filename, modified=True)
@@ -1193,7 +1179,7 @@ class MainController(Controller):
for p in list_of_paths: for p in list_of_paths:
val = model.get_value(model.get_iter(p), 0) val = model.get_value(model.get_iter(p), 0)
ids.append(val) ids.append(val)
for fid in ids: for fid in ids:
# delete from db # delete from db
self.model.delete(fid) self.model.delete(fid)
@@ -1263,31 +1249,6 @@ class MainController(Controller):
pass pass
return return
def on_del_all_images_thumb_activate(self, menu_item):
if self.model.config.confd['delwarn']:
title = 'Delete images'
question = 'Delete all images?'
dsc = "All images without thumbnails will be permanently removed"
dsc += " from catalog."
obj = Dialogs.Qst(title, question, dsc)
if not obj.run():
return
self.model.delete_all_images_wth_thumbs()
self.model.unsaved_project = True
self.__set_title(filepath=self.model.filename, modified=True)
try:
path, column = self.view['files'].get_cursor()
model = self.view['files'].get_model()
fiter = model.get_iter(path)
fid = model.get_value(fiter, 0)
if fid:
self.__get_item_info(fid)
except:
pass
return
def on_del_all_thumb_activate(self, menu_item): def on_del_all_thumb_activate(self, menu_item):
if self.model.config.confd['delwarn']: if self.model.config.confd['delwarn']:
title = 'Delete images' title = 'Delete images'

View File

@@ -157,9 +157,11 @@ class ConfigModel(Model):
dstring = ('cd','ejectapp','imgprog') dstring = ('cd','ejectapp','imgprog')
try: try:
path = os.environ['HOME'] path = os.path.join(os.environ['HOME'], ".pygtktalog")
except: except KeyError:
path = "/tmp" raise KeyError, "Cannot stat path for current user home!"
path = os.path.join(path, "config.ini")
def __init__(self): def __init__(self):
Model.__init__(self) Model.__init__(self)
@@ -179,12 +181,12 @@ class ConfigModel(Model):
def save(self): def save(self):
try: try:
os.lstat("%s/.pygtktalog" % self.path) os.lstat(self.path)
except: except:
print "Saving preferences to %s/.pygtktalog" % self.path print "Saving preferences to %s." % self.path
if __debug__: if __debug__:
print "m_config.py: save() Saving preferences to", print "m_config.py: save() Saving preferences to",
print "%s/.pygtktalog" % self.path print "%s" % self.path
newIni = Ini() newIni = Ini()
# main section # main section
@@ -224,12 +226,12 @@ class ConfigModel(Model):
# write config # write config
try: try:
f = open("%s/.pygtktalog" % self.path,"w") f = open(self.path, "w")
success = True success = True
except: except:
if __debug__: if __debug__:
print "m_config.py: save() Cannot open config file", print "m_config.py: save() Cannot open config file",
print "%s for writing." % (self.path, "/.pygtktalog") print "%s for writing." % self.path
success = False success = False
f.write(newIni.show()) f.write(newIni.show())
f.close() f.close()
@@ -239,7 +241,7 @@ class ConfigModel(Model):
try: try:
# try to read config file # try to read config file
parser = ConfigParser() parser = ConfigParser()
parser.read("%s/.pygtktalog" % self.path) parser.read(self.path)
r = {} r = {}
h = {} h = {}
self.recent = [] self.recent = []

View File

@@ -25,8 +25,9 @@
import os import os
import sys import sys
import shutil import shutil
import tarfile import bz2
import math import math
from tempfile import mkstemp
import gtk import gtk
import gobject import gobject
@@ -47,6 +48,8 @@ except:
from utils.parse_exif import ParseExif from utils.parse_exif import ParseExif
from utils.gthumb import GthumbCommentParser from utils.gthumb import GthumbCommentParser
from utils.no_thumb import no_thumb as no_thumb_img
class MainModel(ModelMT): class MainModel(ModelMT):
"""Create, load, save, manipulate db file which is container for data""" """Create, load, save, manipulate db file which is container for data"""
@@ -91,6 +94,7 @@ class MainModel(ModelMT):
self.unsaved_project = False self.unsaved_project = False
self.filename = None # catalog saved/opened filename self.filename = None # catalog saved/opened filename
self.internal_dirname = None self.internal_dirname = None
self.image_path = None
self.db_connection = None self.db_connection = None
self.db_cursor = None self.db_cursor = None
self.abort = False self.abort = False
@@ -105,6 +109,7 @@ class MainModel(ModelMT):
self.statusmsg = "Idle" self.statusmsg = "Idle"
self.selected_tags = {} self.selected_tags = {}
self.search_created = False self.search_created = False
self.db_tmp_path = False
# Directory tree: id, name, icon, type # Directory tree: id, name, icon, type
self.discs_tree = gtk.TreeStore(gobject.TYPE_INT, self.discs_tree = gtk.TreeStore(gobject.TYPE_INT,
@@ -150,6 +155,30 @@ class MainModel(ModelMT):
# - #rgb # - #rgb
# - #rrggbb # - #rrggbb
self.tag_cloud = [] self.tag_cloud = []
try:
path = os.path.join(os.environ['HOME'], ".pygtktalog")
imgpath = os.path.join(path, "images")
except KeyError:
raise KeyError, "Cannot stat path for current user home!"
if os.path.exists(path):
if not os.path.isdir(path):
raise RuntimeError, "There is regular file \"%s\" on the way. Please remove it." % \
path
else:
os.mkdir(path)
if os.path.exists(imgpath):
if not os.path.isdir(imgpath):
print "Warning:",
"There is regular file \"%s\" on the way. Please remove it, otherwise images cannot be used" % imgpath
else:
os.mkdir(imgpath)
self.image_path = imgpath
self.new() self.new()
return return
@@ -323,49 +352,57 @@ class MainModel(ModelMT):
def add_image(self, image, file_id, only_thumbs=False): def add_image(self, image, file_id, only_thumbs=False):
"""add single image to file/directory""" """add single image to file/directory"""
sql = """INSERT INTO images(file_id, thumbnail, filename) imp = Img(image, self.image_path).save()
VALUES(?, null, null)""" if imp:
self.db_cursor.execute(sql, (file_id,)) # check if there is that image already
self.db_connection.commit() sql = """SELECT filename FROM images WHERE file_id=? and filename=?"""
self.db_cursor.execute(sql, (file_id, imp))
res = self.db_cursor.fetchone()
if res and res[0]:
# there is such an image. going back.
if __debug__:
print res[0]
return
sql = """SELECT id FROM images WHERE thumbnail is null # check if file have have thumbnail. if not, make it with first
AND filename IS null AND file_id=?""" # image
self.db_cursor.execute(sql, (file_id,)) sql = """SELECT id FROM thumbnails WHERE file_id=?"""
res = self.db_cursor.fetchone()
if res:
thp, imp, rec = Img(image, self.internal_dirname).save(res[0])
if rec != -1:
sql = """UPDATE images SET filename=?,
thumbnail=? WHERE id=?"""
if only_thumbs:
os.unlink(imp)
img = None
else:
img = imp.split(self.internal_dirname)[1][1:]
self.db_cursor.execute(sql,
(img,
thp.split(self.internal_dirname)[1][1:],
res[0]))
# check if file have have thumbnail. if not, make it with image
sql = """SELECT id from thumbnails where file_id=?"""
self.db_cursor.execute(sql, (file_id,)) self.db_cursor.execute(sql, (file_id,))
res = self.db_cursor.fetchone() res = self.db_cursor.fetchone()
if not res: thumb = 1
self.add_thumbnail(image, file_id) if not(res and res[0]):
self.db_connection.commit() sql = """INSERT INTO thumbnails(filename, file_id) VALUES(?, ?)"""
self.db_cursor.execute(sql, (imp, file_id))
# insert picture into db
sql = """INSERT INTO images(file_id, filename)
VALUES(?, ?)"""
self.db_cursor.execute(sql, (file_id, imp))
self.db_connection.commit()
## check if file have have thumbnail. if not, make it with image
#sql = """SELECT id from thumbnails where file_id=?"""
#self.db_cursor.execute(sql, (file_id,))
#res = self.db_cursor.fetchone()
#if not res:
# sql = """insert into thumbnails(file_id, filename)
# values (?, ?)"""
# self.db_cursor.execute(sql, (file_id, thp))
# self.db_connection.commit()
#
self.db_connection.commit()
def del_images(self, file_id): def del_images(self, file_id):
"""removes images and their thumbnails from selected file/dir""" """removes images and their thumbnails from selected file/dir"""
# remove images ## remove images
sql = """SELECT filename, thumbnail FROM images WHERE file_id =?""" #sql = """SELECT filename, thumbnail FROM images WHERE file_id =?"""
self.db_cursor.execute(sql, (file_id,)) #self.db_cursor.execute(sql, (file_id,))
res = self.db_cursor.fetchall() #res = self.db_cursor.fetchall()
if len(res) > 0: #if len(res) > 0:
for filen in res: # for filen in res:
if filen[0]: # if filen[0]:
os.unlink(os.path.join(self.internal_dirname, filen[0])) # os.unlink(os.path.join(self.internal_dirname, filen[0]))
os.unlink(os.path.join(self.internal_dirname, filen[1])) # os.unlink(os.path.join(self.internal_dirname, filen[1]))
# remove images records # remove images records
sql = """DELETE FROM images WHERE file_id = ?""" sql = """DELETE FROM images WHERE file_id = ?"""
@@ -380,109 +417,122 @@ class MainModel(ModelMT):
self.db_cursor.execute(sql, (image_id,)) self.db_cursor.execute(sql, (image_id,))
res = self.db_cursor.fetchone() res = self.db_cursor.fetchone()
if res and res[0]: if res and res[0]:
source = os.path.join(self.internal_dirname, res[0]) source = os.path.join(self.image_path, res[0])
count = 1 count = 1
dest = os.path.join(file_path, res[1] + "_%d." % count + \ dest = os.path.join(file_path, res[1] + "_%04d." % count + 'jpg')
res[0].split('.')[-1])
while os.path.exists(dest): while os.path.exists(dest):
count += 1 count += 1
dest = os.path.join(file_path, res[1] + "_%d." % count + \ dest = os.path.join(file_path, res[1] + "_%04d." %\
res[0].split('.')[-1]) count + 'jpg')
if not os.path.exists(source):
return False
shutil.copy(source, dest) shutil.copy(source, dest)
return True return True
#os.unlink()
else: else:
return False return False
def delete_images_wth_thumbs(self, image_id): def delete_images_wth_thumbs(self, image_id):
"""removes image (without thumbnail) on specified image id""" """removes image (without thumbnail) on specified image id"""
sql = """SELECT filename FROM images WHERE id=?""" print "method removed"
self.db_cursor.execute(sql, (image_id,)) #sql = """SELECT filename FROM images WHERE id=?"""
res = self.db_cursor.fetchone() #self.db_cursor.execute(sql, (image_id,))
if res: #res = self.db_cursor.fetchone()
if res[0]: #if res:
os.unlink(os.path.join(self.internal_dirname, res[0])) # if res[0]:
# os.unlink(os.path.join(self.internal_dirname, res[0]))
#
# if __debug__:
# print "m_main.py: delete_image(): removed images:"
# print res[0]
if __debug__:
print "m_main.py: delete_image(): removed images:"
print res[0]
# remove images records # remove images records
sql = """UPDATE images set filename=NULL WHERE id = ?""" #sql = """UPDATE images set filename=NULL WHERE id = ?"""
self.db_cursor.execute(sql, (image_id,)) #self.db_cursor.execute(sql, (image_id,))
self.db_connection.commit() #self.db_connection.commit()
def delete_all_images_wth_thumbs(self): def delete_all_images_wth_thumbs(self):
"""removes all images (without thumbnails) from collection""" """removes all images (without thumbnails) from collection"""
sql = """SELECT filename FROM images""" print "method removed"
self.db_cursor.execute(sql) #sql = """SELECT filename FROM images"""
res = self.db_cursor.fetchall() #self.db_cursor.execute(sql)
for row in res: #res = self.db_cursor.fetchall()
if row[0]: #for row in res:
os.unlink(os.path.join(self.internal_dirname, row[0])) # if row[0]:
if __debug__: # os.unlink(os.path.join(self.internal_dirname, row[0]))
print "m_main.py: delete_all_images(): removed image:", # if __debug__:
print row[0] # print "m_main.py: delete_all_images(): removed image:",
# print row[0]
# remove images records # remove images records
sql = """UPDATE images set filename=NULL""" #sql = """UPDATE images set filename=NULL"""
self.db_cursor.execute(sql) #self.db_cursor.execute(sql)
self.db_connection.commit() #self.db_connection.commit()
def delete_image(self, image_id): def delete_image(self, image_id):
"""removes image on specified image id""" """removes image on specified image id"""
sql = """SELECT filename, thumbnail FROM images WHERE id=?""" #sql = """SELECT filename, thumbnail FROM images WHERE id=?"""
self.db_cursor.execute(sql, (image_id,)) #self.db_cursor.execute(sql, (image_id,))
res = self.db_cursor.fetchone() #res = self.db_cursor.fetchone()
if res: #if res:
if res[0]: # if res[0]:
os.unlink(os.path.join(self.internal_dirname, res[0])) # os.unlink(os.path.join(self.internal_dirname, res[0]))
os.unlink(os.path.join(self.internal_dirname, res[1])) # os.unlink(os.path.join(self.internal_dirname, res[1]))
#
# if __debug__:
# print "m_main.py: delete_image(): removed images:"
# print res[0]
# print res[1]
if __debug__:
print "m_main.py: delete_image(): removed images:"
print res[0]
print res[1]
# remove images records # remove images records
sql = """DELETE FROM images WHERE id = ?""" sql = """DELETE FROM images WHERE id = ?"""
self.db_cursor.execute(sql, (image_id,)) self.db_cursor.execute(sql, (image_id,))
self.db_connection.commit() self.db_connection.commit()
def set_image_as_thumbnail(self, image_id):
"""set image as file thumbnail"""
sql = """SELECT file_id, filename FROM images WHERE id=?"""
self.db_cursor.execute(sql, (image_id,))
res = self.db_cursor.fetchone()
if res and res[0]:
sql = """DELETE FROM thumbnails WHERE file_id=?"""
self.db_cursor.execute(sql, (res[0],))
sql = """INSERT INTO thumbnails(file_id, filename) VALUES(?, ?)"""
self.db_cursor.execute(sql, (res[0], res[1]))
return True
return False
def delete_all_images(self): def delete_all_images(self):
"""removes all images (with thumbnails) from collection""" """removes all images from collection"""
# remove images records # remove images records
sql = """DELETE FROM images""" sql = """DELETE FROM images"""
self.db_cursor.execute(sql) self.db_cursor.execute(sql)
self.db_connection.commit() self.db_connection.commit()
try: #try:
shutil.rmtree(os.path.join(self.internal_dirname, 'images')) # shutil.rmtree(os.path.join(self.internal_dirname, 'images'))
except: #except:
pass # pass
def add_thumbnail(self, img_fn, file_id): def add_thumbnail(self, img_fn, file_id):
"""generate and add thumbnail to selected file/dir""" """generate and add thumbnail to selected file/dir"""
if self.config.confd['thumbs']: if self.config.confd['thumbs']:
self.del_thumbnail(file_id) self.del_thumbnail(file_id)
thumb = Thumbnail(img_fn, base=self.internal_dirname) im, exif = Thumbnail(img_fn, self.image_path).save()
retval = thumb.save(file_id)
if retval[2] != -1: sql = """INSERT INTO thumbnails(file_id, filename) values (?, ?)"""
path = retval[0].split(self.internal_dirname)[1][1:] self.db_cursor.execute(sql, (file_id, im))
sql = """insert into thumbnails(file_id, filename) self.db_connection.commit()
values (?, ?)""" return True
self.db_cursor.execute(sql, (file_id, path))
self.db_connection.commit()
return True
return False return False
def del_thumbnail(self, file_id): def del_thumbnail(self, file_id):
"""removes thumbnail from selected file/dir""" """removes thumbnail from selected file/dir"""
# remove thumbnail files # remove thumbnail files
sql = """SELECT filename FROM thumbnails WHERE file_id=?""" #sql = """SELECT filename FROM thumbnails WHERE file_id=?"""
self.db_cursor.execute(sql, (file_id,)) #self.db_cursor.execute(sql, (file_id,))
res = self.db_cursor.fetchone() #res = self.db_cursor.fetchone()
if res: #if res:
os.unlink(os.path.join(self.internal_dirname, res[0])) # os.unlink(os.path.join(self.internal_dirname, res[0]))
# remove thumbs records # remove thumbs records
sql = """DELETE FROM thumbnails WHERE file_id=?""" sql = """DELETE FROM thumbnails WHERE file_id=?"""
@@ -495,26 +545,33 @@ class MainModel(ModelMT):
sql = """DELETE FROM thumbnails""" sql = """DELETE FROM thumbnails"""
self.db_cursor.execute(sql) self.db_cursor.execute(sql)
self.db_connection.commit() self.db_connection.commit()
try: #try:
shutil.rmtree(os.path.join(self.internal_dirname, 'thumbnails')) # shutil.rmtree(os.path.join(self.internal_dirname, 'thumbnails'))
except: #except:
pass # pass
def cleanup(self): def cleanup(self):
"""remove temporary directory tree from filesystem""" """remove temporary directory tree from filesystem"""
self.__close_db_connection() self.__close_db_connection()
if self.internal_dirname != None: try:
try: os.unlink(self.db_tmp_path)
shutil.rmtree(self.internal_dirname) except:
except OSError: if __debug__:
pass print "Exception in removing temporary db file!"
pass
#if self.internal_dirname != None:
# try:
# shutil.rmtree(self.internal_dirname)
# except OSError:
# pass
return return
def new(self): def new(self):
"""create new project""" """create new project"""
self.unsaved_project = False self.unsaved_project = False
self.filename = None self.filename = None
self.__create_internal_dirname() self.__create_temporary_db_file()
self.__connect_to_db() self.__connect_to_db()
self.__create_database() self.__create_database()
self.__clear_trees() self.__clear_trees()
@@ -525,6 +582,10 @@ class MainModel(ModelMT):
def save(self, filename=None): def save(self, filename=None):
"""save tared directory at given catalog fielname""" """save tared directory at given catalog fielname"""
# flush all changes
self.db_connection.commit()
if not filename and not self.filename: if not filename and not self.filename:
if __debug__: if __debug__:
return False, "no filename detected!" return False, "no filename detected!"
@@ -540,59 +601,86 @@ class MainModel(ModelMT):
def open(self, filename=None): def open(self, filename=None):
"""try to open db file""" """try to open db file"""
self.unsaved_project = False self.unsaved_project = False
self.__create_internal_dirname() self.__create_temporary_db_file()
self.filename = filename self.filename = filename
self.tag_cloud = [] self.tag_cloud = []
self.selected_tags = {} self.selected_tags = {}
self.clear_search_tree() self.clear_search_tree()
try: try:
tar = tarfile.open(filename, "r:gz") test_file = open(filename).read(15)
except: except IOError:
self.filename = None
self.internal_dirname = None
return False
if test_file == "SQLite format 3":
db_tmp = open(self.db_tmp_path, "wb")
db_tmp.write(open(filename).read())
db_tmp.close()
elif test_file[0:10] == "BZh91AY&SY":
open_file = bz2.BZ2File(filename)
try: try:
tar = tarfile.open(filename, "r") curdb = open(self.db_tmp_path, "w")
except: curdb.write(open_file.read())
curdb.close()
open_file.close()
except IOError:
# file is not bz2
self.filename = None self.filename = None
self.internal_dirname = None self.internal_dirname = None
return return False
else:
os.chdir(self.internal_dirname) self.filename = None
try: self.internal_dirname = None
tar.extractall() return False
if __debug__: #try:
print "m_main.py: extracted tarfile into", # tar = tarfile.open(filename, "r:gz")
print self.internal_dirname #except:
except AttributeError: # try:
# python 2.4 tarfile module lacks of method extractall() # tar = tarfile.open(filename, "r")
directories = [] # except:
for tarinfo in tar: # self.filename = None
if tarinfo.isdir(): # self.internal_dirname = None
# Extract directory with a safe mode, so that # return
# all files below can be extracted as well. #
try: #os.chdir(self.internal_dirname)
os.makedirs(os.path.join('.', tarinfo.name), 0700) #try:
except EnvironmentError: # tar.extractall()
pass # if __debug__:
directories.append(tarinfo) # print "m_main.py: extracted tarfile into",
else: # print self.internal_dirname
tar.extract(tarinfo, '.') #except AttributeError:
# # python 2.4 tarfile module lacks of method extractall()
# Reverse sort directories. # directories = []
directories.sort(lambda a, b: cmp(a.name, b.name)) # for tarinfo in tar:
directories.reverse() # if tarinfo.isdir():
# # Extract directory with a safe mode, so that
# Set correct owner, mtime and filemode on directories. # # all files below can be extracted as well.
for tarinfo in directories: # try:
try: # os.makedirs(os.path.join('.', tarinfo.name), 0700)
os.chown(os.path.join('.', tarinfo.name), # except EnvironmentError:
tarinfo.uid, tarinfo.gid) # pass
os.utime(os.path.join('.', tarinfo.name), # directories.append(tarinfo)
(0, tarinfo.mtime)) # else:
except OSError: # tar.extract(tarinfo, '.')
if __debug__: #
print "m_main.py: open(): setting corrext owner,", # # Reverse sort directories.
print "mtime etc" # directories.sort(lambda a, b: cmp(a.name, b.name))
tar.close() # directories.reverse()
#
# # Set correct owner, mtime and filemode on directories.
# for tarinfo in directories:
# try:
# os.chown(os.path.join('.', tarinfo.name),
# tarinfo.uid, tarinfo.gid)
# os.utime(os.path.join('.', tarinfo.name),
# (0, tarinfo.mtime))
# except OSError:
# if __debug__:
# print "m_main.py: open(): setting corrext owner,",
# print "mtime etc"
#tar.close()
self.__connect_to_db() self.__connect_to_db()
self.__fetch_db_into_treestore() self.__fetch_db_into_treestore()
@@ -858,9 +946,9 @@ class MainModel(ModelMT):
"""get file info from database""" """get file info from database"""
retval = {} retval = {}
sql = """SELECT f.filename, f.date, f.size, f.type, sql = """SELECT f.filename, f.date, f.size, f.type,
t.filename, f.description, f.note f.description, f.note, t.filename
FROM files f FROM files f
LEFT JOIN thumbnails t ON t.file_id = f.id LEFT JOIN thumbnails t on f.id = t.file_id
WHERE f.id = ?""" WHERE f.id = ?"""
self.db_cursor.execute(sql, (file_id,)) self.db_cursor.execute(sql, (file_id,))
res = self.db_cursor.fetchone() res = self.db_cursor.fetchone()
@@ -868,31 +956,37 @@ class MainModel(ModelMT):
retval['fileinfo'] = {'id': file_id, retval['fileinfo'] = {'id': file_id,
'date': datetime.fromtimestamp(res[1]), 'date': datetime.fromtimestamp(res[1]),
'size': res[2], 'type': res[3]} 'size': res[2], 'type': res[3]}
retval['fileinfo']['disc'] = self.__get_file_root(file_id) retval['fileinfo']['disc'] = self.__get_file_root(file_id)
retval['filename'] = res[0] retval['filename'] = res[0]
if res[4]:
retval['description'] = res[4]
if res[5]: if res[5]:
retval['description'] = res[5] retval['note'] = res[5]
if res[6]: if res[6]:
retval['note'] = res[6] thumbfile = os.path.join(self.image_path, res[6] + "_t")
if os.path.exists(thumbfile):
pix = gtk.gdk.pixbuf_new_from_file(thumbfile)
retval['thumbnail'] = thumbfile
if res[4]: sql = """SELECT id, filename FROM images
path = os.path.join(self.internal_dirname, res[4])
retval['thumbnail'] = path
sql = """SELECT id, thumbnail FROM images
WHERE file_id = ?""" WHERE file_id = ?"""
self.db_cursor.execute(sql, (file_id,)) self.db_cursor.execute(sql, (file_id,))
res = self.db_cursor.fetchall() res = self.db_cursor.fetchall()
if res: if res:
self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf) self.images_store = gtk.ListStore(gobject.TYPE_INT, gtk.gdk.Pixbuf)
for idi, thb in res: for im_id, filename in res:
img = os.path.join(self.internal_dirname, thb) thumbfile = os.path.join(self.image_path, filename + "_t")
pix = gtk.gdk.pixbuf_new_from_file(img) if os.path.exists(thumbfile):
self.images_store.append([idi, pix]) pix = gtk.gdk.pixbuf_new_from_file(thumbfile)
else:
pix = gtk.gdk.pixbuf_new_from_inline(len(no_thumb_img),
no_thumb_img, False)
self.images_store.append([im_id, pix])
retval['images'] = True retval['images'] = True
sql = """SELECT camera, date, aperture, exposure_program, sql = """SELECT camera, date, aperture, exposure_program,
@@ -998,23 +1092,23 @@ class MainModel(ModelMT):
arg = str(tuple(fids)) arg = str(tuple(fids))
# remove thumbnails # remove thumbnails
sql = """SELECT filename FROM thumbnails WHERE file_id IN %s""" % arg #sql = """SELECT filename FROM thumbnails WHERE file_id IN %s""" % arg
db_cursor.execute(sql) #db_cursor.execute(sql)
res = db_cursor.fetchall() #res = db_cursor.fetchall()
if len(res) > 0: #if len(res) > 0:
for row in res: # for row in res:
os.unlink(os.path.join(self.internal_dirname, row[0])) # os.unlink(os.path.join(self.image_path, row[0]))
# remove images # remove images
sql = """SELECT filename, thumbnail FROM images #sql = """SELECT filename, thumbnail FROM images
WHERE file_id IN %s""" % arg # WHERE file_id IN %s""" % arg
db_cursor.execute(sql) #db_cursor.execute(sql)
res = db_cursor.fetchall() #res = db_cursor.fetchall()
if len(res) > 0: #if len(res) > 0:
for row in res: # for row in res:
if row[0]: # if row[0]:
os.unlink(os.path.join(self.internal_dirname, row[0])) # os.unlink(os.path.join(self.internal_dirname, row[0]))
os.unlink(os.path.join(self.internal_dirname, row[1])) # os.unlink(os.path.join(self.internal_dirname, row[1]))
# remove thumbs records # remove thumbs records
sql = """DELETE FROM thumbnails WHERE file_id = ?""" sql = """DELETE FROM thumbnails WHERE file_id = ?"""
@@ -1053,7 +1147,7 @@ class MainModel(ModelMT):
parent_id = False parent_id = False
db_connection.commit() db_connection.commit()
# part two: remove items from treestore/liststores # part two: remove items from treestore/liststores
def foreach_treestore(model, path, iterator, d): def foreach_treestore(model, path, iterator, d):
if d[0] == model.get_value(iterator, 0): if d[0] == model.get_value(iterator, 0):
@@ -1160,9 +1254,10 @@ class MainModel(ModelMT):
sql = """SELECT filename FROM images WHERE id=?""" sql = """SELECT filename FROM images WHERE id=?"""
self.db_cursor.execute(sql, (img_id,)) self.db_cursor.execute(sql, (img_id,))
res = self.db_cursor.fetchone() res = self.db_cursor.fetchone()
if res: if res and res[0]:
if res[0]: path = os.path.join(self.image_path, res[0])
return os.path.join(self.internal_dirname, res[0]) if os.path.exists(path):
return path
return None return None
def update_desc_and_note(self, file_id, desc='', note=''): def update_desc_and_note(self, file_id, desc='', note=''):
@@ -1278,14 +1373,14 @@ class MainModel(ModelMT):
def __connect_to_db(self): def __connect_to_db(self):
"""initialize db connection and store it in class attributes""" """initialize db connection and store it in class attributes"""
self.db_connection = sqlite.connect("%s" % \ self.db_connection = sqlite.connect(self.db_tmp_path,
(self.internal_dirname + '/db.sqlite'),
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
self.db_cursor = self.db_connection.cursor() self.db_cursor = self.db_connection.cursor()
return return
def __close_db_connection(self): def __close_db_connection(self):
"""close db conection""" """close db conection"""
if self.db_cursor != None: if self.db_cursor != None:
self.db_cursor.close() self.db_cursor.close()
self.db_cursor = None self.db_cursor = None
@@ -1294,42 +1389,66 @@ class MainModel(ModelMT):
self.db_connection = None self.db_connection = None
return return
def __create_internal_dirname(self): def __create_temporary_db_file(self):
"""create temporary directory for working thumb/image files and """create temporary db file"""
database"""
# TODO: change this stupid rutine into tempfile mkdtemp method
self.cleanup() self.cleanup()
self.internal_dirname = "/tmp/pygtktalog%d" % \ self.db_tmp_path = mkstemp()[1]
datetime.now().microsecond
try:
os.mkdir(self.internal_dirname)
except IOError, (errno, strerror):
print "m_main.py: __create_internal_dirname(): ", strerror
return return
def __compress_and_save(self): def __compress_and_save(self):
"""create (and optionaly compress) tar archive from working directory """create (and optionaly compress) tar archive from working directory
and write it to specified file""" and write it to specified file"""
# flush all changes
self.db_connection.commit()
try: try:
if self.config.confd['compress']: if self.config.confd['compress']:
tar = tarfile.open(self.filename, "w:gz") output_file = bz2.BZ2File(self.filename, "w")
else: else:
tar = tarfile.open(self.filename, "w") output_file = open(self.filename, "w")
if __debug__: if __debug__:
print "m_main.py: __compress_and_save(): tar open successed" print "m_main.py: __compress_and_save(): tar open successed"
except IOError, (errno, strerror): except IOError, (errno, strerror):
return False, strerror return False, strerror
os.chdir(self.internal_dirname) dbpath = open(self.db_tmp_path)
tar.add('.') output_file.write(dbpath.read())
tar.close() dbpath.close()
output_file.close()
self.unsaved_project = False self.unsaved_project = False
return True, None return True, None
def __create_database(self): def __create_database(self):
"""make all necessary tables in db file""" """make all necessary tables in db file
,------------. ,------------.
|files | |tags |
+------------+ +------------+
|→|pk id | |pk id |
|_|fk parent_id| |fk group_id |
|filename | |tag |
|filepath | +------------+
|date |
|size | ,------------
|source | |tags_files
|note | |
|description | |
+------------+ |
"""
self.db_cursor.execute("""create table self.db_cursor.execute("""create table
files(id INTEGER PRIMARY KEY AUTOINCREMENT, files(id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_id INTEGER, parent_id INTEGER,
@@ -1359,7 +1478,6 @@ class MainModel(ModelMT):
self.db_cursor.execute("""create table self.db_cursor.execute("""create table
images(id INTEGER PRIMARY KEY AUTOINCREMENT, images(id INTEGER PRIMARY KEY AUTOINCREMENT,
file_id INTEGER, file_id INTEGER,
thumbnail TEXT,
filename TEXT);""") filename TEXT);""")
self.db_cursor.execute("""create table self.db_cursor.execute("""create table
exif(id INTEGER PRIMARY KEY AUTOINCREMENT, exif(id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1463,8 +1581,7 @@ class MainModel(ModelMT):
self.busy = True self.busy = True
# new conection for this task, because it's running in separate thread # new conection for this task, because it's running in separate thread
db_connection = sqlite.connect("%s" % \ db_connection = sqlite.connect(self.db_tmp_path,
(self.internal_dirname + '/db.sqlite'),
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES, detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES,
isolation_level="EXCLUSIVE") isolation_level="EXCLUSIVE")
db_cursor = db_connection.cursor() db_cursor = db_connection.cursor()
@@ -1627,15 +1744,13 @@ class MainModel(ModelMT):
# Images - thumbnails and exif data # Images - thumbnails and exif data
if self.config.confd['thumbs'] and ext in self.IMG: if self.config.confd['thumbs'] and ext in self.IMG:
t = Thumbnail(current_file, thumb = Thumbnail(current_file, self.image_path)
base=self.internal_dirname) th, exif = thumb.save()
tpath, exif, ret_code = t.save(fileid) if th:
if ret_code != -1:
sql = """INSERT INTO sql = """INSERT INTO
thumbnails(file_id, filename) thumbnails(file_id, filename)
VALUES(?, ?)""" VALUES(?, ?)"""
t = tpath.split(self.internal_dirname)[1][1:] db_cursor.execute(sql, (fileid, th))
db_cursor.execute(sql, (fileid, t))
# exif - store data in exif table # exif - store data in exif table
jpg = ['jpg', 'jpeg'] jpg = ['jpg', 'jpeg']
@@ -1750,8 +1865,7 @@ class MainModel(ModelMT):
#connect #connect
detect_types = sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES detect_types = sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES
db_connection = sqlite.connect("%s" % \ db_connection = sqlite.connect(self.db_tmp_path,
(self.internal_dirname + '/db.sqlite'),
detect_types = detect_types) detect_types = detect_types)
db_cursor = db_connection.cursor() db_cursor = db_connection.cursor()
@@ -1803,8 +1917,7 @@ class MainModel(ModelMT):
"""append branch from DB to existing tree model""" """append branch from DB to existing tree model"""
#connect #connect
detect_types = sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES detect_types = sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES
db_connection = sqlite.connect("%s" % db_connection = sqlite.connect(self.db_tmp_path,
(self.internal_dirname + '/db.sqlite'),
detect_types = detect_types) detect_types = detect_types)
db_cursor = db_connection.cursor() db_cursor = db_connection.cursor()

View File

@@ -280,8 +280,8 @@ class ChooseDBFilename(object):
f = gtk.FileFilter() f = gtk.FileFilter()
f.set_name("Catalog files") f.set_name("Catalog files")
f.add_pattern("*.pgt") f.add_pattern("*.sqlite")
f.add_pattern("*.pgt.tgz") f.add_pattern("*.sqlite.bz2")
self.dialog.add_filter(f) self.dialog.add_filter(f)
f = gtk.FileFilter() f = gtk.FileFilter()
f.set_name("All files") f.set_name("All files")
@@ -299,13 +299,9 @@ class ChooseDBFilename(object):
if response == gtk.RESPONSE_OK: if response == gtk.RESPONSE_OK:
filename = self.dialog.get_filename() filename = self.dialog.get_filename()
print filename, ' do ', print filename, ' do ',
if filename[-8:].lower() != '.pgt.tgz' and \ if filename[-11:].lower() != '.sqlite.bz2' and \
filename[-4:].lower() != '.pgt': filename[-7:].lower() != '.sqlite':
filename = filename + '.pgt.tgz' filename = filename + '.sqlite.bz2'
elif filename[-4:].lower() == '.pgt':
filename = filename[:-4] + '.pgt.tgz'
else:
filename = filename[:-8] + '.pgt.tgz'
print filename print filename
self.__class__.URI = self.dialog.get_current_folder_uri() self.__class__.URI = self.dialog.get_current_folder_uri()
self.dialog.destroy() self.dialog.destroy()
@@ -323,7 +319,7 @@ class LoadDBFile(object):
def __init__(self, path=None): def __init__(self, path=None):
self.path = path self.path = path
self.dialog = gtk.FileChooserDialog( self.dialog = gtk.FileChooserDialog(
title="Open catalog", title="Open catalog",
action=gtk.FILE_CHOOSER_ACTION_OPEN, action=gtk.FILE_CHOOSER_ACTION_OPEN,
@@ -334,11 +330,11 @@ class LoadDBFile(object):
gtk.RESPONSE_OK)) gtk.RESPONSE_OK))
self.dialog.set_default_response(gtk.RESPONSE_OK) self.dialog.set_default_response(gtk.RESPONSE_OK)
f = gtk.FileFilter() f = gtk.FileFilter()
f.set_name("Catalog files") f.set_name("Catalog files")
f.add_pattern("*.pgt") f.add_pattern("*.sqlite")
f.add_pattern("*.pgt.tgz") f.add_pattern("*.sqlite.bz2")
self.dialog.add_filter(f) self.dialog.add_filter(f)
f = gtk.FileFilter() f = gtk.FileFilter()
f.set_name("All files") f.set_name("All files")