Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorroot <root@ghunt-desktop.(none)>2010-09-30 22:45:35 (GMT)
committer root <root@ghunt-desktop.(none)>2010-09-30 22:45:35 (GMT)
commit8d1072faf04a36b382af85e750143569bd21bfec (patch)
treed49aead294e25173aebfb7f9570699c97699c516
parente27f087758e900341bfe9ef590b8db661656cd80 (diff)
upgrade database structure, write xml metadata along with images to outputs
-rw-r--r--dbphoto.py63
-rw-r--r--display.py84
-rw-r--r--photo_toolbar.py30
-rw-r--r--sinks.py107
-rw-r--r--sources.py102
-rw-r--r--xophoto.sqlite.templatebin12288 -> 12288 bytes
-rw-r--r--xophotoactivity.py37
7 files changed, 348 insertions, 75 deletions
diff --git a/dbphoto.py b/dbphoto.py
index fded745..deabc98 100644
--- a/dbphoto.py
+++ b/dbphoto.py
@@ -19,6 +19,22 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
+""" General introduction to XoPhoto database
+Two limitations have guided the layout of the databases:
+ 1. Datastore is very slow reading or writing large data chunks
+ 2.Minimal processing horsepower makes thumbnail generation slow and suggests that
+ thumbnails should be stored in the persistant data storage area assigned
+ to each activity.
+So there are really two sqlite databases associated with XoPhoto. xophoto.sqlite is
+stored in the journal, and holds the user state information -- Album stacks are stored
+in a table 'groups'. The table 'config' remembers last operations.
+
+The much larger database 'data_cache.sqlite' is stored in the persistent data area
+available to XoPhoto. I houses the thumbnail blobs and the necessary data about each
+image found in the datastore.
+
+
+"""
from gettext import gettext as _
import os
@@ -143,11 +159,13 @@ class DbAccess():
if is_journal: #is_journal: #want most recent first, need left join because picture may not exist yet
#sql = """select groups.*, data_cache.picture.* from groups left join data_cache.picture \
#where groups.category = ? and groups.jobject_id = data_cache.picture.jobject_id order by groups.seq desc"""
- sql = """select groups.* from groups where groups.category = ? order by groups.seq desc"""
+ sql = """select groups.*, picture.* from groups, picture where groups.category = ?
+ and groups.jobject_id = picture.jobject_id order by groups.seq desc"""
else:
#sql = """select groups.*, data_cache.picture.* from groups left join data_cache.picture \
#where groups.category = ? and groups.jobject_id = data_cache.picture.jobject_id order by groups.seq """
- sql = "select * from groups where category = ? order by seq"
+ sql = """select groups.*, picture.* from groups, picture where category = ?
+ and groups.jobject_id = picture.jobject_id order by seq"""
cursor = self.connection().cursor()
cursor.execute(sql,(str(album_id),))
return cursor.fetchall()
@@ -155,7 +173,7 @@ class DbAccess():
def get_thumbnail_count(self,album_id):
conn = self.connection()
cursor = conn.cursor()
- cursor.execute('select count(*) as count from groups where category = ?',(str(album_id),))
+ cursor.execute('select count(*) as count from groups,picture where category = ? and groups.jobject_id = picture.jobject_id',(str(album_id),))
rows = cursor.fetchall()
if len(rows) == 1:
return rows[0]['count']
@@ -226,22 +244,27 @@ class DbAccess():
return 1
elif len(rows) == 1:
sql = """update data_cache.picture set in_ds = ?, mount_point = ?, orig_size = ?, \
- create_date = ?, md5_sum = ?, duplicate = ?"""
+ create_date = ?, md5_sum = ?"""
cursor = self.connection().cursor()
- cursor.execute(sql,(1, fn, info.st_size, info.st_ctime, md5_hash,len(rows_md5)))
+ cursor.execute(sql,(1, fn, info.st_size, info.st_ctime, md5_hash,))
self.con.commit()
_logger.debug('%s seconds to update'%(time.clock()-start))
return 0
def is_picture_in_ds(self,fn):
+ if not fn: return None
md5_hash = Md5Tools().md5sum(fn)
+ return self.is_md5_in_picture(md5_hash)
+
+ def is_md5_in_picture(self,md5_hash):
+ if not md5_hash: return None
sql = "select * from data_cache.picture where md5_sum = '%s'"%(md5_hash,)
conn = self.connection()
cur = conn.cursor()
cur.execute(sql)
rows_md5 = cur.fetchall()
if len(rows_md5) >0:
- return True
+ return rows_md5[0]
return False
@@ -256,6 +279,7 @@ class DbAccess():
self.con.commit()
def set_title_in_picture(self,jobject_id,title):
+ """question: should title,comment default to last entered?"""
if not jobject_id: return #during startup, jobject not yet set
sql = "select * from data_cache.picture where jobject_id = '%s'"%(jobject_id,)
cur = self.connection().cursor()
@@ -267,6 +291,9 @@ class DbAccess():
cursor.execute(sql,(title,jobject_id,))
self.con.commit()
+ def set_title_in_groups(self,jobject_id,title):
+ self.set_title_in_picture(jobject_id,title)
+
def get_title_in_picture(self,jobject_id):
if not jobject_id: return #during startup, jobject not yet set
sql = "select * from data_cache.picture where jobject_id = '%s'"%(jobject_id,)
@@ -289,6 +316,9 @@ class DbAccess():
cursor.execute(sql,(comment,jobject_id,))
self.con.commit()
+ def set_comment_in_groups(self,jobject_id,comment):
+ self.set_comment_in_picture(jobject_id,comment)
+
def get_comment_in_picture(self,jobject_id):
if not jobject_id: return #during startup, jobject not yet set
sql = "select * from data_cache.picture where jobject_id = '%s'"%(jobject_id,)
@@ -300,6 +330,16 @@ class DbAccess():
return None
+ def get_picture_rec_for_id(self,jobject_id):
+ if not jobject_id: return #during startup, jobject not yet set
+ sql = "select * from data_cache.picture where jobject_id = '%s'"%(jobject_id,)
+ cur = self.connection().cursor()
+ cur.execute(sql)
+ rows = cur.fetchall()
+ if len(rows) == 1:
+ return rows[0]
+ return None
+
def clear_in_ds(self):
self.connection().execute('update data_cache.picture set in_ds = 0')
@@ -363,6 +403,16 @@ class DbAccess():
except:
return 0
+ def get_album_name_from_timestamp(self,timestamp):
+ if not timestamp: return None
+ cursor = self.connection().cursor()
+ cursor.execute("select * from groups where category = 'albums' and subcategory = ?",(str(timestamp),))
+ rows = cursor.fetchall()
+ if len(rows) == 1:
+ return rows[0]['stack_name']
+ return None
+
+
def create_update_album(self,album_id,name):
conn = self.connection()
cursor = conn.cursor()
@@ -536,6 +586,7 @@ values (?,?,?,?,?,?,?,?,?)""",(jobject_id,w,h,x_thumb,y_thumb,thumb_binary,trans
return ''
def change_album_id(self,from_id,to_id):
+ if from_id == to_id: return
conn = self.connection()
cur = conn.cursor()
cur.execute('select * from groups where category = ?',(from_id,))
diff --git a/display.py b/display.py
index 97b86da..2e1ee03 100644
--- a/display.py
+++ b/display.py
@@ -76,9 +76,14 @@ album_height = 190
album_size = (180,165)
album_location = (25,25)
album_aperature = (150,125)
+slideshow_title_font_size = 60
+slideshow_comment_font_size = 40
+slideshow_dwell = 3
startup_clock = 0
journal_id = '20100521T10:42'
+menu_journal_label = _('Journal Title: ')
+menu_stack_label = _('Stack Name: ')
trash_id = '20100521T11:40'
@@ -523,18 +528,13 @@ class OneAlbum():
return surf
def put_image_on_stack(self,album_id):
- if album_id == journal_id:
- sql = "select * from groups where category = '%s' order by seq asc"%(album_id)
- elif album_id == trash_id:
- return #let trash image alone
- else:
- sql = "select * from groups where category = '%s' order by seq desc"%(album_id)
- (rows,cur) = self.db.dbdo(sql)
+ if album_id == trash_id:
+ return
+ rows = self.db.get_album_thumbnails(album_id,True)
if len(rows)>0:
jobject_id = str(rows[0]['jobject_id'])
else:
jobject_id = None
- _logger.debug('failed to get jobject_id:%s for display on album side (stack).sql:%s'%(jobject_id,sql,))
return None
conn = self.db.get_connection()
@@ -893,17 +893,16 @@ class DisplayAlbums():
_logger.debug('exception fetching thumbnails %s'%e)
return
self.selected_album_id = album_timestamp
+ self._activity.activity_toolbar.empty_journal_button.hide()
if album_timestamp == trash_id:
self._activity.activity_toolbar.empty_journal_button.show()
+ self._activity.activity_toolbar.set_label(display.menu_journal_label,False)
+ elif album_timestamp == journal_id:
+ self._activity.activity_toolbar.set_label(display.menu_journal_label,True)
+ self._activity.activity_toolbar.title.set_text(self._activity.metadata.get('title'))
+
else:
- self._activity.activity_toolbar.empty_journal_button.hide()
- #_logger.debug('now display album at index %s thumbnails with the album identifier %s'%
- #(self.album_index,album_timestamp))
- change_name = True
- for id,name in self.predefined_albums:
- if album_timestamp == id:
- change_name = False
- if change_name:
+ self._activity.activity_toolbar.set_label(display.menu_stack_label,True)
self._activity.activity_toolbar.title.set_text(album_title)
self.display_thumbnails(album_timestamp,new_surface=True)
pygame.display.flip()
@@ -1145,13 +1144,19 @@ class DisplayAlbums():
return
from_album_id = self.get_album_id_at_index(from_index)
to_album_id = self.get_album_id_at_index(to_index)
- if to_album_id == journal_id: return #silently do nothing
+ if to_album_id == journal_id: return #don't transfer anything to journal
if to_album_id == trash_id: #this is a album delete-all request
- caption = _('Caution -- This is a multiple image delete.')
- message = _('Please confirm that you want to transfer all these images to the Trash')
- alert = self._activity.util.confirmation_alert(message,caption,self.trash_them_cb)
- alert.from_album_id = from_album_id
- return
+ if from_album_id != journal_id:
+ caption = _('Caution -- This is a multiple image delete.')
+ message = _('Please confirm that you want to transfer all these images to the Trash')
+ alert = self._activity.util.confirmation_alert(message,caption,self.trash_them_cb)
+ alert.from_album_id = from_album_id
+ return
+ else:
+ caption = _('Not Allowed!! Just too easy to loose everything!')
+ message = _('If you really want to delete everything, create a stack, and drag it to Trash, then empty the Trash.')
+ alert = self._activity.util.alert(message,caption)
+ return
if from_album_id == journal_id or from_album_id == trash_id:
#issue a no and exit
caption = _('Not Permitted')
@@ -1171,6 +1176,7 @@ class DisplayAlbums():
def trash_them_cb(self,alert,confirmation):
if not confirmation == gtk.RESPONSE_OK: return
#go ahead and move them to the trash, then display trash
+ if alert.from_album_id == trash_id or alert.from_album_id == journal_id: return
self.db.change_album_id(alert.from_album_id,trash_id)
self.delete_album(alert.from_album_id,trash_id)
@@ -1429,9 +1435,14 @@ class Application():
def view_slides(self):
album_object = self.set_album_for_viewslides()
if album_object:
- self.vs.run()
+ self.vs.running = True
+ self.vs.paused = False
+ self.vs.get_large_screen()
+ self._activity.fullscreen()
+ self.vs.show_title = True
+ self.vs.next_slide()
#on return from viewing slides, restore the normal screen
- album_object.repaint_whole_screen()
+ #album_object.repaint_whole_screen()
def set_album_for_viewslides(self):
album_id = self.album_collection.selected_album_id
@@ -1495,15 +1506,21 @@ class Application():
while gtk.events_pending():
gtk.main_iteration()
- # Pump PyGame messages.
+ #if viewing slides, do different things
+ if self.vs.running or self.vs.paused:
+ self.vs.run()
+ continue
+
+ # else fall through to do the main loop stuff.
for event in pygame.event.get():
if event.type in (MOUSEBUTTONDOWN,MOUSEBUTTONUP,MOUSEMOTION):
x,y = event.pos
if event.type == KEYUP:
print event
if event.key == K_ESCAPE:
- running = False
- pygame.quit()
+ #running = False
+ #pygame.quit()
+ pass
elif event.key == K_LEFT:
self.album_collection.album_objects[self.album_collection.selected_album_id].prev()
pygame.display.flip()
@@ -1536,10 +1553,13 @@ class Application():
elif event.type == MOUSEMOTION:
self.drag(event)
elif event.type == MOUSEBUTTONUP:
+ if not in_grab and self._activity and self._activity.window:
+ self._activity.window.set_cursor(None)
if in_drag:
self.drop(event)
if event.type == pygame.QUIT:
- return
+ pass
+ #return
elif event.type == pygame.VIDEORESIZE:
pygame.display.set_mode(event.size, pygame.RESIZABLE)
@@ -1628,6 +1648,7 @@ class Application():
if r:
self.album_collection.start_grab(x,y)
else:
+ self.album_collection.set_hand_cursor()
if x < album_column_width -thick:
#scroll_x,scroll_y = self.album_collection.album_sb.get_scrolled()
rtn_val = self.album_collection.click(x,y)
@@ -1672,9 +1693,10 @@ class Application():
global in_click_delay
in_click_delay = False
- def end_db_delay(self):
- global in_db_wait
- in_db_wait = False
+ def is_journal(self):
+ if self.album_collection.selected_album_id == journal_id:
+ return True
+ return False
class shim():
def __init__(self):
diff --git a/photo_toolbar.py b/photo_toolbar.py
index 0ee0fba..94428f2 100644
--- a/photo_toolbar.py
+++ b/photo_toolbar.py
@@ -28,6 +28,8 @@ from sugar.graphics.toolcombobox import ToolComboBox
from sugar.graphics.toolbutton import ToolButton
from gettext import gettext as _
+import display
+
class ActivityToolbar(gtk.Toolbar):
"""The Activity toolbar with the Journal entry title, sharing,
Keep and Stop buttons
@@ -48,9 +50,9 @@ class ActivityToolbar(gtk.Toolbar):
"""
#if activity.metadata:
if True:
- label = gtk.Label(_('New Album Name: '))
- label.show()
- self._add_widget(label)
+ self.label = gtk.Label(display.menu_journal_label)
+ self.label.show()
+ self._add_widget(self.label)
self.title = gtk.Entry()
self.title.set_size_request(int(gtk.gdk.screen_width() / 6), -1)
@@ -130,9 +132,15 @@ class ActivityToolbar(gtk.Toolbar):
self.stop.connect('clicked', self.__stop_clicked_cb)
self.insert(self.stop, -1)
self.stop.show()
-
self._update_title_sid = None
-
+
+ def set_label(self,text,visible=True):
+ self.label.set_text(text)
+ if not visible:
+ self.title.set_sensitive(False)
+ else:
+ self.title.set_sensitive(True)
+
def _update_share(self):
self._updating_share = True
@@ -190,10 +198,14 @@ class ActivityToolbar(gtk.Toolbar):
def __update_title_cb(self, entry=None):
title = self.title.get_text()
-
- self._activity.metadata['title'] = title
- self._activity.metadata['title_set_by_user'] = '1'
- self._activity.game.change_album_name(title)
+ if self._activity.game.is_journal():
+ self._activity.metadata['title'] = title
+ self._activity.metadata['title_set_by_user'] = '1'
+ else:
+ self._activity.game.change_album_name(title)
+ title_set_by_user = self._activity.metadata.get('title_set_by_user')
+ if not title_set_by_user: #let the journal title reflect the most recent stack
+ self._activity.metadata['title'] = title
self._update_title_sid = None
return False
diff --git a/sinks.py b/sinks.py
index 3e13cbe..c0aa837 100644
--- a/sinks.py
+++ b/sinks.py
@@ -27,6 +27,7 @@ import sys, os
import gtk
import shutil
import gobject
+from xml.etree.cElementTree import Element, ElementTree, SubElement
#application imports
from dbphoto import *
@@ -48,11 +49,14 @@ class ViewSlides():
self.db = None
self.paused = False
self.loop = True
- self.dwell_time = 3
gobject.timeout_add(1000, self.__timeout)
self.current_time = 1 #set so the first call of timeout will initiate action
self.running = False
self.paint = None
+ self.is_resized = False
+ self.show_title = False
+ self.show_default = True
+ self.is_fullscreen = False
#display.screen.fill((0,0,0))
#pygame.display.flip()
@@ -71,12 +75,12 @@ class ViewSlides():
return True
self.current_time -= 1
if self.current_time == 0:
- self.current_time = self.dwell_time
+ self.current_time = display.slideshow_dwell
self.display_next()
return True
def display_next(self):
- self.current_time = self.dwell_time
+ self.current_time = display.slideshow_dwell
if self.index < 0 or self.index >= len(self.rows):
_logger.debug('display_next index out of bounds')
return
@@ -96,10 +100,56 @@ class ViewSlides():
def display_large(self):
self.album_object.large_displayed = True
+ title = None
+ if self.is_fullscreen:
+ self.get_large_screen()
+ self.title_panel.fill((0,0,0))
+
+ title = self._parent.db.get_title_in_picture(self.rows[self.index]['jobject_id'])
+ if self.show_title and title or self.show_default:
+ font = pygame.font.Font(None,display.slideshow_title_font_size)
+ if title:
+ show_title = title
+ else:
+ default_title = _('Title for this Picture Will Appear Here')
+ show_title = default_title
+ text = font.render('%s'%(show_title,),0,(255,255,255))
+ text_rect = text.get_rect()
+ text_rect.midtop = self.title_panel.get_rect().midtop
+ self.title_panel.blit(text,text_rect)
+
+ comment = self._parent.db.get_comment_in_picture(self.rows[self.index]['jobject_id'])
+ if comment or self.show_default:
+ self.show_default = False
+ font = pygame.font.Font(None,display.slideshow_comment_font_size)
+ if comment:
+ show_comment = comment
+ else:
+ default_comment = _('Use Up/Down to toggle text, space to Pause/Play, left/right for manual changes ')
+ show_comment = default_comment
+ text = font.render('%s'%(show_comment,),0,(255,255,255))
+ text_rect = text.get_rect()
+ text_rect.midbottom = self.title_panel.get_rect().midbottom
+ self.title_panel.blit(text,text_rect)
+
+ display.screen.blit(self.title_panel,(0,display.screen_h))
+ _logger.debug('screen blit with title:%s'%(title,))
display.screen.blit(self.paint,(0,0))
pygame.display.flip()
+ def get_large_screen(self):
+ self.is_fullscreen = True
+ if not self.is_resized:
+ self.is_resized = True
+ y = gtk.gdk.screen_height()
+ size_y = y - display.screen_h
+ x = gtk.gdk.screen_width()
+ display.screen = pygame.display.set_mode((x,y),pygame.RESIZABLE)
+ _logger.debug('title panel request:(%s,%s)'%(x,size_y,))
+ self.title_panel = pygame.Surface((x,size_y))
+ self.title_panel.fill((0,0,0))
+
def transform_scale_slide(self,jobject_id):
"""return surface transformed per database transforms,onto screen sized target"""
_logger.debug('entered transform_scale_slide')
@@ -155,7 +205,8 @@ class ViewSlides():
def place_picture(self,source,target):
"""return surface centered and scaled,but not blitted to target"""
target_rect = target.get_rect()
- screen_w,screen_h = target_rect.size
+ #screen_w,screen_h = target_rect.size
+ screen_w,screen_h = display.screen_w,display.screen_h
target_surf = pygame.Surface((screen_w,screen_h))
image_rect = source.get_rect()
screen_aspect = float(screen_w)/screen_h
@@ -185,13 +236,11 @@ class ViewSlides():
self._parent.util.alert(_('Please select a stack that contains images'),_('Cannot show a slideshow with no images'))
self._parent._activity.use_toolbar.set_running(False)
return
- self.running = True
- self.paused = False
- _logger.debug('started the run loop')
- while self.running:
+ #while self.running:
+ if True:
# Pump GTK messages.
- while gtk.events_pending():
- gtk.main_iteration()
+ #while gtk.events_pending():
+ #gtk.main_iteration()
# Pump PyGame messages.
for event in pygame.event.get():
@@ -202,11 +251,15 @@ class ViewSlides():
if event.key == K_ESCAPE:
self.running = False
elif event.key == K_LEFT:
- if self.index > 1:
- self.index -= 2
- self.display_next()
+ self.prev_slide()
elif event.key == K_RIGHT:
self.display_next()
+ elif event.key == K_UP:
+ self.show_title = True
+ elif event.key == K_DOWN:
+ self.show_title = False
+ elif event.key == K_SPACE:
+ self.paused = not self.paused
def pause(self):
self.paused = True
@@ -221,7 +274,6 @@ class ViewSlides():
def next_slide(self):
self.index = self.album_object.thumb_index
-
self.display_next()
def play(self):
@@ -230,6 +282,7 @@ class ViewSlides():
def stop(self):
self.running = False
+ self.paused = False
#'gtk.STOCK_MEDIA_STOP'
self.album_object.large_displayed = False
self.album_object.repaint_whole_screen()
@@ -250,6 +303,7 @@ class ExportAlbum():
self.path = path
self.album = str(path.split('/')[-1])
self.name = name
+ self.eroot = Element('root')
def do_export(self):
disable_write = False
@@ -275,6 +329,7 @@ class ExportAlbum():
lookup = {'image/png':'.png','image/jpg':'.jpg','image/jpeg':'.jpg','image/gif':'.gif','image/tif':'.tif'}
ok_exts = ['png','jpg','jpeg','jpe','gif','tif',]
for row in self.rows:
+ timestamp = row['category']
jobject_id = row['jobject_id']
ds_object = datastore.get(jobject_id)
if not ds_object:
@@ -295,8 +350,9 @@ class ExportAlbum():
base = base.replace(' ','_')
else:
base = self.name + lookup.get(mime_type,'')
- base = '%03d'%index +'_' + base
+ base = 'img_%03d'%index +'_' + base
_logger.debug('exporting %s to %s'%(fn,os.path.join(self.path,base),))
+ cur_element = SubElement(self.eroot,base)
if not disable_write:
shutil.copy(fn,os.path.join(self.path,base))
ds_object.destroy()
@@ -307,21 +363,40 @@ class ExportAlbum():
if md:
if title:
md['title'] = title
+ cur_element.attrib['title'] = title
if description:
md['description'] = description
+ cur_element.attrib['comment'] = description
tag = md.get('tags','')
if len(tag) == 0:
md['tags'] = self.album
else:
if tag.find(self.album) < 0:
md['tags'] = md['tags'] + ', ' +self.album
+ cur_element.attrib['export_dir'] = self.album
+ name = self.db.get_album_name_from_timestamp(row['category'])
+ if name:
+ cur_element.attrib['stack_name'] = name
try:
datastore.write(ds_object)
except Exception, e:
_logger.debug('datastore write exception %s'%e)
ds_object.destroy()
index += 1
- if disable_write:
+
+ #add data from the pictue table
+ pict_row = self.db.get_picture_rec_for_id(jobject_id)
+ if pict_row:
+ cur_element.attrib['md5'] = pict_row['md5_sum']
+ cur_element.attrib['mount_point'] = pict_row['mount_point']
+ cur_element.attrib['orig_size'] = str(int(pict_row['orig_size']))
+
+ if not disable_write:
+ cur_element = SubElement(self.eroot,'album_timestamp',)
+ cur_element.text = timestamp
+ fn = os.path.join(self.path,"xophoto.xml")
+ ElementTree(self.eroot).write(fn)
+ else:
self._parent.game.util.alert(_('Write permission not set for path ')+
'%s'%self.base_path+
_(' Please see help for instructions to correct this problem.'),
diff --git a/sources.py b/sources.py
index c1e0e95..0a90bb4 100644
--- a/sources.py
+++ b/sources.py
@@ -34,6 +34,7 @@ import gtk
import shutil
import sqlite3
import time
+from xml.etree.cElementTree import Element, ElementTree, SubElement
from dbphoto import *
import display
@@ -125,9 +126,11 @@ class Datastore_SQLite():
added = 0
a_row_found = False
cursor = self.db.connection().cursor()
+ journal_list = []
for ds in ds_list:
#at least for now assume that the newest images are returned first
if not a_row_found:
+ journal_list.append(ds.object_id)
dict = ds.get_metadata().get_dictionary()
if dict["mime_type"] in mime_list:
cursor.execute('select * from groups where category = ? and jobject_id = ?',\
@@ -135,7 +138,7 @@ class Datastore_SQLite():
rows = cursor.fetchall()
if len(rows) == 0:
#may need to add date entered into ds (create date could be confusing)
- #self.db.put_ds_into_picture(ds.object_id)
+ self.db.put_ds_into_picture(ds.object_id)
self.db.add_image_to_album(display.journal_id,ds.object_id)
added += 1
else: #assume that pictures are returned in last in first out order
@@ -143,6 +146,8 @@ class Datastore_SQLite():
#a_row_found = True
pass
ds.destroy()
+ #now go through albums and remove references that are no longer in datastore
+ #cursor.execute('select * from groups')
_logger.debug('scan found %s. Added %s datastore object ids from datastore to picture'%(count,added,))
return (num_found,added,)
@@ -270,6 +275,7 @@ class FileTree():
def copy_list_to_ds(self,file_list):
"""receives list of absolute file names to be copied to datastore"""
added = 0
+ jobject_id_list = {}
reserve_at_least = 50000000L #don't fill the last 50M of the journal
#reserve_at_least = 5000000000L #force it to complain for testing
self.cancel = False
@@ -278,7 +284,7 @@ class FileTree():
#is the requested set of images going to fit in the XO datastore?
tot = 0.0
- acceptable_extensions = ['jpg','jpeg','png','gif']
+ acceptable_extensions = ['jpg','jpeg','png','gif','jpe','tif']
for filename in file_list:
chunks = filename.split('.')
ext = ''
@@ -299,30 +305,56 @@ class FileTree():
self._activity.add_alert(alert)
_logger.debug('total free space message:%s free space:%d tot:%d'%(message,free_space,tot,))
return
+
+ #is there a xml information file in the directory where these photos are stored?
+ base = os.path.dirname(file_list[0]) #make assumption that list is all in a single directory
+ xml_path = os.path.join(base,'xophoto.xml')
+ if os.path.isfile(xml_path):
+ xml_data = self.get_xml(xml_path)
+ else:
+ xml_data = None
+
+ #let the user know progress of the import
num = len(file_list)
message = _('Number of images to copy to the XO Journal: ') + str(num)
pa_title = _('Please be patient')
alert = display.ProgressAlert(msg=message,title=pa_title)
self._activity.add_alert(alert)
alert.connect('response',self._response_cb)
+
for filename in file_list:
start = time.clock()
mtype = ''
chunks = filename.split('.')
if len(chunks)>1:
ext = chunks[-1]
- if ext == 'jpg' or ext == 'jpeg':
+ if ext == 'jpg' or ext == 'jpeg' :
mtype = 'image/jpeg'
elif ext == 'gif':
mtype = 'image/gif'
elif ext == 'png':
mtype = 'image/png'
if mtype == '': continue
- info = os.stat(filename)
- size = info.st_size
+ #info = os.stat(filename)
+ #size = info.st_size
+ #check if this image md5_sum is already loaded
+ if xml_data:
+ found = xml_data.findall(os.path.basename(filename))
+ if found:
+ md5 = found[0].attrib.get('md5_sum',None)
+ md5_row = self.db.is_md5_in_picture(md5)
+ if md5 and md5_row:
+ _logger.debug('md5 match from xml to picture table')
+ jobject_id_list[filename] = md5_row['jobject_id']
+ continue
+
#if the md5_sum is already in ds, abort
- if self.db.is_picture_in_ds(filename): continue
+ md5_row = self.db.is_picture_in_ds(filename)
+ if md5_row:
+ jobject_id_list[filename] = md5_row['jobject_id']
+ continue
+
ds = datastore.create()
ds.metadata['filename'] = filename
ds.metadata['title'] = os.path.basename(filename)
@@ -332,6 +364,7 @@ class FileTree():
ds.set_file_path(dest)
datastore.write(ds,transfer_ownership=True)
self.db.create_picture_record(ds.object_id,filename)
+ jobject_id_list[filename] = ds.object_id
ds.destroy()
_logger.debug('writing one image to datastore took %f seconds'%(time.clock()-start))
added += 1
@@ -340,10 +373,65 @@ class FileTree():
gtk.main_iteration()
if self.cancel:
break
+
+ #create an album for this import
+ self.create_album_for_import(file_list,xml_data,jobject_id_list)
+
_logger.debug('writing all images to datastore took %f seconds'%(time.clock()-proc_start))
self._activity.remove_alert(alert)
return added
-
+
+ def dict_dump(self,dct):
+ ret = ''
+ for key in dct.keys():
+ ret += '%s:%s | '%(key,dct[key])
+ return ret
+
+ def create_album_for_import(self,file_list,xml_data,jobject_id_list):
+ _logger.debug('create album for import received jobject list %s'%self.dict_dump(jobject_id_list))
+ timestamp = None
+ name = None
+ if xml_data:
+ found = xml_data.findall('album_timestamp')
+ if found:
+ timestamp = found[0].text
+ if timestamp == display.journal_id or timestamp == display.trash_id:
+ timestamp = None
+ if not timestamp:
+ timestamp = str(datetime.datetime.today())
+ _logger.debug('timestamp:%s'%timestamp)
+ for file_name in file_list:
+ jobject_id = jobject_id_list.get(file_name)
+ self.db.add_image_to_album(timestamp,jobject_id)
+ if xml_data:
+ found = xml_data.findall(os.path.basename(file_name))
+ if found:
+ _logger.debug('create album xml data %s'%self.dict_dump(found[0].attrib))
+ title = found[0].attrib.get('title')
+ if title:
+ self.db.set_title_in_groups(jobject_id,title)
+ comment = found[0].attrib.get('comment')
+ if comment:
+ self.db.set_comment_in_groups(jobject_id,comment)
+ name = found[0].attrib.get('stack_name')
+ if not name:
+ name = _('Camera Roll')
+ self.db.create_update_album(timestamp,name)
+ self._activity.game.album_collection.album_objects[timestamp] = \
+ display.OneAlbum(self.db,timestamp,self._activity.game.album_collection)
+ #set the image on the top of the stack to the last one in the seqence
+ self._activity.game.album_collection.album_objects[timestamp].set_top_image(jobject_id)
+ #save off the unique id(timestamp)as a continuing target
+ self.db.set_last_album(timestamp)
+
+ def get_xml(self,xml_path):
+ try:
+ tree = ElementTree(file=xml_path).getroot()
+ except Exception,e:
+ _logger.debug('get_xml parse error: %s'%e)
+ return None
+ return tree
+
if __name__ == '__main__':
db = DbAccess('/home/olpc/.sugar/default/org.laptop.XoPhoto/data/xophoto.sqlite')
if db.is_open():
diff --git a/xophoto.sqlite.template b/xophoto.sqlite.template
index b7c059d..0f326cc 100644
--- a/xophoto.sqlite.template
+++ b/xophoto.sqlite.template
Binary files differ
diff --git a/xophotoactivity.py b/xophotoactivity.py
index e4ad2c5..90b1b80 100644
--- a/xophotoactivity.py
+++ b/xophotoactivity.py
@@ -166,7 +166,7 @@ class XoPhotoActivity(activity.Activity):
#self.activity_toolbar.share.props.visible = False
self.edit_toolbar = EditToolbar(self)
- self.toolbox.add_toolbar(_('Edit'), self.edit_toolbar)
+ self.toolbox.add_toolbar(_('Input'), self.edit_toolbar)
self.edit_toolbar.connect('do-import',
self.edit_toolbar_doimport_cb)
self.edit_toolbar.connect('do-initialize',
@@ -392,7 +392,7 @@ class XoPhotoActivity(activity.Activity):
exporter.do_export()
def use_toolbar_do_fullscreen_cb(self,use_toolbar):
-
+ self.game.vs.get_large_screen()
self.fullscreen()
def use_toolbar_doslideshow_cb(self,use_toolbar):
@@ -402,6 +402,7 @@ class XoPhotoActivity(activity.Activity):
def use_toolbar_do_rewind_cb(self,use_toolbar):
self.game.set_album_for_viewslides()
+ self.game.vs.pause()
self.game.vs.prev_slide()
def use_toolbar_do_pause_cb(self,use_toolbar):
@@ -414,6 +415,7 @@ class XoPhotoActivity(activity.Activity):
def use_toolbar_do_forward_cb(self,use_toolbar):
self.game.set_album_for_viewslides()
+ self.game.vs.pause()
self.game.vs.next_slide()
def use_toolbar_do_slideshow_stop_cb(self,use_toolbar):
@@ -635,7 +637,11 @@ class XoPhotoActivity(activity.Activity):
_logger.debug('database failed to re-open in write file. error:%s'%e)
exit()
_logger.debug('sqlite datbase re-opened successfully')
-
+
+ def unfullscreen(self):
+ """this overrides the gtk.window function inherited by activity.Activity"""
+ super(activity.Activity,self).unfullscreen()
+ self.game.vs.is_fullscreen = False
class EditToolbar(gtk.Toolbar):
__gtype_name__ = 'EditToolbar'
@@ -815,6 +821,7 @@ class UseToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
+ self._update_dwell_sid = None
self.doexport = photo_toolbar.ImageButton()
fn = os.path.join(os.getcwd(),'assets','stack_export.png')
tooltip = _('Export to USB/SD/DISK')
@@ -833,7 +840,7 @@ class UseToolbar(gtk.Toolbar):
self.doupload.set_tooltip(_('Fullscreen'))
self.doupload.connect('clicked', self.doupload_cb)
self.insert(self.doupload, -1)
- self.doupload.show()
+ self.doupload.hide()
separator = gtk.SeparatorToolItem()
separator.props.draw = False
@@ -887,12 +894,14 @@ class UseToolbar(gtk.Toolbar):
self.dwell_entry = gtk.Entry()
self.dwell_entry.set_width_chars(2)
+ self.dwell_entry.set_text(str(display.slideshow_dwell))
tool_item = gtk.ToolItem()
tool_item.set_expand(False)
tool_item.add(self.dwell_entry)
self.dwell_entry.show()
+ self.dwell_entry.connect('changed',self.__dwell_changed_cb)
self.insert(tool_item, -1)
- tool_item.hide()
+ tool_item.show()
separator = gtk.SeparatorToolItem()
separator.props.draw = True
@@ -943,7 +952,23 @@ class UseToolbar(gtk.Toolbar):
self.do_rewind.set_sensitive(True)
self.do_slideshow_stop.set_sensitive(True)
-
+ def __dwell_changed_cb(self, entry):
+ if not self._update_dwell_sid:
+ self._update_dwell_sid = gobject.timeout_add(
+ 1000, self.__update_dwell_cb)
+
+ def __update_dwell_cb(self, entry=None):
+ dwell = self.dwell_entry.get_text()
+ try: #only respond to integer values
+ new_dwell = int(dwell)
+ except:
+ self.dwell_entry.set_text(str(display.slideshow_dwell))
+ return False
+ _logger.debug('new dwell %s'%(new_dwell,))
+ self.dwell_entry.text = str(new_dwell)
+ display.slideshow_dwell = new_dwell
+ self._update_dwell_sid = None
+ return False
def doexport_cb(self, button):
self.emit('do-export')