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

* Lots of changes, code cleanup, import/export in GUI.

This commit is contained in:
2008-12-15 20:40:24 +00:00
parent c2926c96d6
commit 0adcdaba8d
20 changed files with 195 additions and 243 deletions

31
README
View File

@@ -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 <http://www.nongnu.org/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
====

View File

@@ -142,7 +142,7 @@ def setup_path():
"""Sets up the python include paths to include needed directories"""
import os.path
from src.utils.globals import TOPDIR
from src.lib.globs import TOPDIR
sys.path = [os.path.join(TOPDIR, "src")] + sys.path
return
@@ -169,7 +169,7 @@ if __name__ == "__main__":
from shutil import copy
from utils.img import Img
from lib.img import Img
from models.m_main import MainModel as NewModel
model = OldModel()

View File

@@ -27,18 +27,16 @@ try:
import gtk
except ImportError:
print "You need to install pyGTK v2.10.x or newer."
sys.exit(1)
raise
def setup_path():
"""Sets up the python include paths to include needed directories"""
import os.path
from src.utils.globals import TOPDIR
from src.lib.globs import TOPDIR
sys.path = [os.path.join(TOPDIR, "src")] + sys.path
return
def check_requirements():
"""Checks versions and other requirements"""
import sys
@@ -50,7 +48,7 @@ def check_requirements():
except ImportError:
print "Some fundamental files are missing.",
print "Try runnig pyGTKtalog in his root directory"
sys.exit(1)
raise
conf = ConfigModel()
conf.load()
@@ -64,13 +62,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"
raise
if conf.confd['exportxls']:
try:
@@ -98,7 +100,7 @@ if __name__ == "__main__":
os.chdir(libraries_dir)
setup_path()
check_requirements()
#check_requirements()
from models.m_main import MainModel
from ctrls.c_main import MainController
@@ -113,6 +115,6 @@ if __name__ == "__main__":
try:
gtk.main()
except KeyboardInterrupt:
model.config.save()
model.cleanup()
#model.config.save()
#model.cleanup()
gtk.main_quit

View File

@@ -62,6 +62,27 @@
<property name="visible">True</property>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="import">
<property name="visible">True</property>
<property name="label" translatable="yes">Import</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_import_activate"/>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="export">
<property name="visible">True</property>
<property name="label" translatable="yes">Export</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_export_activate"/>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="separator13">
<property name="visible">True</property>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="recent_files1">
<property name="visible">True</property>

View File

@@ -22,27 +22,26 @@
# -------------------------------------------------------------------------
__version__ = "1.0 RC2"
LICENCE = open('LICENCE').read()
import os.path
from os import popen
from utils import deviceHelper
from gtkmvc import Controller
from c_config import ConfigController
from views.v_config import ConfigView
from c_search import SearchController
from views.v_search import SearchView
import views.v_dialogs as Dialogs
from views.v_image import ImageView
import gtk
import pango
from gtkmvc import Controller
from lib import device_helper
from lib.globs import APPL_VERSION
from c_config import ConfigController
from views.v_config import ConfigView
from c_search import SearchController
from views.v_search import SearchView
import views.v_dialogs as Dialogs
from views.v_image import ImageView
class MainController(Controller):
"""Controller for main application window"""
scan_cd = False
@@ -526,9 +525,9 @@ class MainController(Controller):
def on_add_cd_activate(self, widget, label=None, current_id=None):
"""Add directory structure from cd/dvd disc"""
mount = deviceHelper.volmount(self.model.config.confd['cd'])
mount = device_helper.volmount(self.model.config.confd['cd'])
if mount == 'ok':
guessed_label = deviceHelper.volname(self.model.config.confd['cd'])
guessed_label = device_helper.volname(self.model.config.confd['cd'])
if not label:
label = Dialogs.InputDiskLabel(guessed_label).run()
if label:
@@ -541,7 +540,7 @@ class MainController(Controller):
self.model.unsaved_project = True
self.__set_title(filepath=self.model.filename, modified=True)
else:
deviceHelper.volumount(self.model.config.confd['cd'])
device_helper.volumount(self.model.config.confd['cd'])
return True
else:
Dialogs.Wrn("Error mounting device - pyGTKtalog",
@@ -571,7 +570,7 @@ class MainController(Controller):
# NOTE: about
def on_about1_activate(self, widget):
"""Show about dialog"""
Dialogs.Abt("pyGTKtalog", __version__, "About",
Dialogs.Abt("pyGTKtalog", "%d.%d.%d" % APPL_VERSION, "About",
["Roman 'gryf' Dobosz"], LICENCE)
return
@@ -871,6 +870,16 @@ 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
# TODO: dokończyć ten shit
def on_collapse_all1_activate(self, menu_item):
self.view['discs'].collapse_all()
return
@@ -1354,7 +1363,7 @@ class MainController(Controller):
# umount/eject cd
ejectapp = self.model.config.confd['ejectapp']
if self.model.config.confd['eject'] and ejectapp:
msg = deviceHelper.eject_cd(ejectapp,
msg = device_helper.eject_cd(ejectapp,
self.model.config.confd['cd'])
if msg != 'ok':
Dialogs.Wrn("error ejecting device - pyGTKtalog",
@@ -1362,7 +1371,7 @@ class MainController(Controller):
self.model.config.confd['cd'],
"Last eject message:\n%s" % msg)
else:
msg = deviceHelper.volumount(self.model.config.confd['cd'])
msg = device_helper.volumount(self.model.config.confd['cd'])
if msg != 'ok':
Dialogs.Wrn("error unmounting device - pyGTKtalog",
"Cannot unmount device pointed to %s" %

View File

@@ -21,91 +21,71 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# -------------------------------------------------------------------------
"""
device (cd, dvd) helper
"""
import os
def volname(mntp):
"""read volume name from cd/dvd"""
dev = mountpoint_to_dev(mntp)
if dev != None:
try:
a = open(dev,"rb")
a.seek(32808)
b = a.read(32).strip()
a.close()
except:
disk = open(dev, "rb")
disk.seek(32808)
label = disk.read(32).strip()
disk.close()
except IOError:
return None
return b
return label
return None
def volmount(mntp):
"""mount device, return 'ok' or error message"""
_in,_out,_err = os.popen3("mount %s" % mntp)
_in, _out, _err = os.popen3("mount %s" % mntp)
inf = _err.readlines()
if len(inf) > 0:
for i in inf:
i.strip()
return i
return inf[0].strip()
else:
return 'ok'
def volumount(mntp):
"""mount device, return 'ok' or error message"""
_in,_out,_err = os.popen3("umount %s" % mntp)
_in, _out, _err = os.popen3("umount %s" % mntp)
inf = _err.readlines()
if len(inf) > 0:
for error in inf:
error.strip()
if error.strip()[:7] == 'umount:':
return error.strip()
return inf[0].strip()
return 'ok'
def check_mount(dev):
"""Refresh the entries from fstab or mount."""
mounts = os.popen('mount')
for line in mounts.readlines():
parts = line.split()
device, txt1, mount_point, txt2, filesystem, options = parts
if device == dev:
device = parts
if device[0] == dev:
return True
return False
def mountpoint_to_dev(mntp):
"""guess mountpoint from fstab"""
"""guess device name by mountpoint from fstab"""
fstab = open("/etc/fstab")
device = None
for line in fstab.readlines():
a = line.split()
try:
if a[1] == mntp and a[0][0] != '#':
fstab.close()
return a[0]
except:
pass
output = line.split()
# lengtht of single valid fstab line is at least 5
if len(output) > 5 and output[1] == mntp and output[0][0] != '#':
device = output[0]
fstab.close()
return None
return device
def eject_cd(eject_app, cd):
def eject_cd(eject_app, cdrom):
"""mount device, return 'ok' or error message"""
if len(eject_app) > 0:
_in,_out,_err = os.popen3("%s %s" % (eject_app, cd))
_in, _out, _err = os.popen3("%s %s" % (eject_app, cdrom))
inf = _err.readlines()
error = ''
for error in inf:
error.strip()
if error !='':
return error
if len(inf) > 0 and inf[0].strip() != '':
return inf[0].strip()
return 'ok'
return "Eject program not specified"

View File

@@ -25,15 +25,17 @@
import os.path
import sys
if sys.argv[0]: top_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
else: top_dir = "."
if sys.argv[0]:
top_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
else:
top_dir = "."
# ----------------------------------------------------------------------
TOPDIR = top_dir
RESOURCES_DIR = os.path.join(TOPDIR, "resources")
GLADE_DIR = os.path.join(RESOURCES_DIR, "glade")
STYLES_DIR = os.path.join(RESOURCES_DIR, "styles")
APPL_SHORT_NAME = "pycolector"
APPL_VERSION = (1, 0, 0)
APPL_SHORT_NAME = "pygtktalog2"
APPL_VERSION = (1, 0, 1)
# ----------------------------------------------------------------------

View File

@@ -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):
@@ -53,7 +51,8 @@ class Img(object):
# 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):

View File

@@ -28,7 +28,7 @@ from shutil import move
from os import path
import sys
from utils import EXIF
from lib import EXIF
import Image
class Thumbnail(object):

View File

@@ -34,21 +34,25 @@ import gobject
from gtkmvc.model_mt import ModelMT
from pysqlite2 import dbapi2 as sqlite
try:
import sqlite3 as sqlite
except ImportError:
from pysqlite2 import dbapi2 as sqlite
from datetime import datetime
import threading as _threading
from m_config import ConfigModel
try:
from utils.thumbnail import Thumbnail
from utils.img import Img
from lib.thumbnail import Thumbnail
from lib.img import Img
except:
pass
from utils.parse_exif import ParseExif
from utils.gthumb import GthumbCommentParser
from lib.parse_exif import ParseExif
from lib.gthumb import GthumbCommentParser
from utils.no_thumb import no_thumb as no_thumb_img
from lib.no_thumb import no_thumb as no_thumb_img
class MainModel(ModelMT):
"""Create, load, save, manipulate db file which is container for data"""
@@ -592,6 +596,14 @@ class MainModel(ModelMT):
return
if filename:
if not '.sqlite' in filename:
filename += '.sqlite'
else:
filename = filename[:filename.rindex('.sqlite')] + '.sqlite'
if self.config.confd['compress']:
filename += '.bz2'
self.filename = filename
val, err = self.__compress_and_save()
if not val:

View File

@@ -1,112 +0,0 @@
# This Python file uses the following encoding: utf-8
#
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
#
# Copyright (C) 2007 by Roman 'gryf' Dobosz
#
# This file is part of pyGTKtalog.
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# -------------------------------------------------------------------------
"""
device (cd, dvd) helper
"""
import os
def volname(mntp):
"""read volume name from cd/dvd"""
dev = mountpoint_to_dev(mntp)
if dev != None:
try:
a = open(dev,"rb")
a.seek(32808)
b = a.read(32).strip()
a.close()
except:
return None
return b
return None
def volmount(mntp):
"""mount device, return 'ok' or error message"""
_in,_out,_err = os.popen3("mount %s" % mntp)
inf = _err.readlines()
if len(inf) > 0:
for i in inf:
i.strip()
return i
else:
return 'ok'
def volumount(mntp):
"""mount device, return 'ok' or error message"""
_in,_out,_err = os.popen3("umount %s" % mntp)
inf = _err.readlines()
if len(inf) > 0:
for error in inf:
error.strip()
if error.strip()[:7] == 'umount:':
return error.strip()
return 'ok'
def check_mount(dev):
"""Refresh the entries from fstab or mount."""
mounts = os.popen('mount')
for line in mounts.readlines():
parts = line.split()
device, txt1, mount_point, txt2, filesystem, options = parts
if device == dev:
return True
return False
def mountpoint_to_dev(mntp):
"""guess mountpoint from fstab"""
fstab = open("/etc/fstab")
for line in fstab.readlines():
a = line.split()
try:
if a[1] == mntp and a[0][0] != '#':
fstab.close()
return a[0]
except:
pass
fstab.close()
return None
def eject_cd(eject_app, cd):
"""mount device, return 'ok' or error message"""
if len(eject_app) > 0:
_in,_out,_err = os.popen3("%s %s" % (eject_app, cd))
inf = _err.readlines()
error = ''
for error in inf:
error.strip()
if error !='':
return error
return 'ok'
return "Eject program not specified"

View File

@@ -24,11 +24,11 @@
from gtkmvc import View
import os.path
import utils.globals
import lib.globs
class ConfigView(View):
"""Preferences window from glade file """
GLADE = os.path.join(utils.globals.GLADE_DIR, "config.glade")
GLADE = os.path.join(lib.globs.GLADE_DIR, "config.glade")
def __init__(self, ctrl):
View.__init__(self, ctrl, self.GLADE)

View File

@@ -25,7 +25,7 @@
import gtk
import gobject
import os
import utils.globals
import lib.globs
class Qst(object):
"""Show simple dialog for questions
@@ -128,7 +128,7 @@ class InputDiskLabel(object):
"""Sepcific dialog for quering user for a disc label"""
def __init__(self, label=""):
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
self.label = ""
if label!= None:
self.label = label
@@ -148,7 +148,7 @@ class InputNewName(object):
"""Sepcific dialog for quering user for a disc label"""
def __init__(self, name=""):
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
self.label = ""
self.name = name
@@ -169,7 +169,7 @@ class PointDirectoryToAdd(object):
URI="file://"+os.path.abspath(os.path.curdir)
def __init__(self,volname='',dirname=''):
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
self.gladexml = gtk.glade.XML(self.gladefile, "addDirDialog")
self.volname = self.gladexml.get_widget("dirvolname")
self.volname.set_text(volname)
@@ -257,15 +257,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 +276,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 +335,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
@@ -454,7 +486,7 @@ class StatsDialog(object):
"""Sepcific dialog for display stats"""
def __init__(self, values={}):
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
self.values = values
def run(self):
@@ -497,7 +529,7 @@ class TagsDialog(object):
"""Sepcific dialog for display stats"""
def __init__(self):
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
def run(self):
gladexml = gtk.glade.XML(self.gladefile, "tagsDialog")
@@ -516,7 +548,7 @@ class TagsRemoveDialog(object):
"""Sepcific dialog for display stats"""
def __init__(self, tag_dict=None):
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
self.tag_dict = tag_dict
def run(self):
@@ -585,7 +617,7 @@ class EditDialog(object):
"""Sepcific dialog for display stats"""
def __init__(self, values={}):
self.gladefile = os.path.join(utils.globals.GLADE_DIR, "dialogs.glade")
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
self.values = values
def run(self):

View File

@@ -23,14 +23,14 @@
# -------------------------------------------------------------------------
import os.path
import utils.globals
import lib.globs
from gtkmvc import View
class MainView(View):
"""This handles only the graphical representation of the
application. The widgets set is loaded from glade file"""
GLADE = os.path.join(utils.globals.GLADE_DIR, "main.glade")
GLADE = os.path.join(lib.globs.GLADE_DIR, "main.glade")
def __init__(self, ctrl):
View.__init__(self, ctrl, self.GLADE)

View File

@@ -24,12 +24,12 @@
from gtkmvc import View
import os.path
import utils.globals
import lib.globs
class SearchView(View):
"""Search window from glade file """
GLADE = os.path.join(utils.globals.GLADE_DIR, "search.glade")
GLADE = os.path.join(lib.globs.GLADE_DIR, "search.glade")
def __init__(self, ctrl):
View.__init__(self, ctrl, self.GLADE)