From 8d1072faf04a36b382af85e750143569bd21bfec Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Sep 2010 22:45:35 +0000 Subject: upgrade database structure, write xml metadata along with images to outputs --- 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') -- cgit v0.9.1