--- zpu-0.2/zpu.py 2008-12-30 04:21:15.000000000 -0330 +++ zpu-0.3A2/zpu.py 2009-01-02 14:12:54.000000000 -0330 @@ -3,6 +3,7 @@ # zpu - ZenPhoto picture upload utility # # Copyright (C) 2008 Josep Sanjuas Cuxart +# Copyright (C) 2008 Peter Gill peter@majorsilence.com # # 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 @@ -20,6 +21,7 @@ import sys, os, glob, time, gobject, threading from mechanize import Browser +from PIL import Image try: import pygtk @@ -32,22 +34,164 @@ except: sys.exit(1) +def custom_image_filter(filter_info=None, data=None): + """Custom file filter. Filter for video files when running on win32""" + image_types=["jpg", "jpeg", "JPG", "JPEG", "png", "bmp", "gif", "tiff", "raw"] + if os.path.splitext(filter_info[2])[1][1:] in image_types: + return True + return False + +def message_dialog(msg): + dialog = gtk.MessageDialog(parent = None, + buttons = gtk.BUTTONS_CLOSE, flags = gtk.DIALOG_DESTROY_WITH_PARENT, + type = gtk.MESSAGE_INFO, message_format = msg) + dialog.set_title("Info") + result = dialog.run() + dialog.destroy() + +# get the name of the config file to initially load the config +def get_config_file(): + try: + homedir = "" + if sys.platform == 'win32': + homedir=os.environ["USERPROFILE"] + else: + homedir=os.environ["HOME"] + except Exception: + print "Error: home directory not found. Please set $HOME" + os._exit(-1) + config_path = os.path.join(homedir, '.zenphotouploader.rc') + if os.path.exists(config_path) == False: + new_file = open(config_path, "w") + new_file.write("") + new_file.close() + return os.path.join(homedir, '.zenphotouploader.rc') + + + +class ZPSettings: + server = 'http://' + user = 'user' + passwd = 'pass' + def __init__(self): + self.load_config() + + def get_server(self): + return ZPSettings.server + + def set_server(self, s): + ZPSettings.server = s + + def get_user(self): + return ZPSettings.user + + def set_user(self, u): + ZPSettings.user = u -class Uploader: + def get_password(self): + return ZPSettings.passwd - # callbacks for the album selection + def set_password(self, p): + ZPSettings.passwd = p + + # try to load the config from a config file. + def load_config(self): + configfile = get_config_file() + dict = {} + + print "load config", configfile + try: + execfile(configfile, dict) + ZPSettings.server = dict['server'] + ZPSettings.user = dict['user'] + ZPSettings.passwd = dict['password'] + except Exception, inst: + print "exc" + print Exception, inst + + # save the current configuration. + def save_config(self): + cfg = get_config_file() + f = open(cfg, "w") + f.write("server = '%s'\n" % ZPSettings.server) + f.write("user = '%s'\n" % ZPSettings.user) + f.write("password = '%s'\n" % ZPSettings.passwd) + f.close() + +class ZPAboutDialog: + def __init__(self): + newtree=gtk.glade.XML("zpu.glade", "aboutdialog1") + window=newtree.get_widget("aboutdialog1") + window.show() + window.run() + window.hide() + window.destroy() + window=None + +class ZPPreferences: + def __init__(self): + newtree=gtk.glade.XML("zpu.glade", "Preferences") + newtree.signal_autoconnect(self) + window=newtree.get_widget("Preferences") + window.show_all() + self.server = newtree.get_widget("server_text") + self.user = newtree.get_widget("user_text") + self.password = newtree.get_widget("pass_text") + self.settings = ZPSettings() + #settings.load_config() + self.server.set_text(self.settings.get_server()) + self.user.set_text(self.settings.get_user()) + self.password.set_text(self.settings.get_password()) + self.password.set_visibility(False) + + def on_apply_preferences_clicked(self, widget): + self.settings.set_server(self.server.get_text()) + self.settings.set_user(self.user.get_text()) + self.settings.set_password(self.password.get_text()) + self.settings.save_config() + +class ZPProgress: + def __init__(self, parent_window): + self.progress_status = -1 + self.parent_window= parent_window + newtree=gtk.glade.XML("zpu.glade", "UploadProgress") + newtree.signal_autoconnect(self) + self.window=newtree.get_widget("UploadProgress") + self.progressbar = newtree.get_widget("progressbar1") + self.cancel_button = newtree.get_widget("cancel_upload") + self.pause_button = newtree.get_widget("pause_upload") + self.close_button = newtree.get_widget("close_progress_window") + + self.window.show_all() + + def set_progress_status(self, status): + self.progress_status = status + + # update the progress bar + def progress_update(self): + if self.progress_status == -1: + self.close_button.hide() + self.progressbar.set_fraction(0) + elif self.progress_status == -2: + self.close_button.set_property("visible", True) + self.cancel_button.set_property("visible", False) + self.pause_button.set_property("visible", False) + self.progressbar.set_text("Finished Uploading") + self.progressbar.set_fraction(1.0) + #self.window.emit("delete-event", gtk.gdk.Event(gtk.gdk.DELETE)) + else: + self.progressbar.set_text("%.0f%%" % (self.progress_status * 100)) + self.progressbar.set_fraction(self.progress_status) + return True - def on_ok_clicked(self, widget): - self.ac_album = self.wTree.get_widget("albums").get_active_text() - self.ac_status = "ok" - def on_cancel_clicked(self, widget): - self.ac_status = "cancel" + def on_close_progress_window_clicked(self, widget=None): + self.window.destroy() + self.parent_window.show_all() - def on_quit_clicked(self, widget): - pass + def on_UploadProgress_delete_event(self, widget=None, event=None): + self.parent_window.show_all() - def on_save_button_clicked(self, widget): - self.save_config() +class ZPUploader: # utility function to upload a file to an album @@ -64,17 +208,25 @@ album_name = item.name else: item.selected = False - - if album_name == None: - print "error: album not found !?\n" - os._exit(1) + + #if album_name == None: + # print "error: album not found !?\n" + # os._exit(1) br.form.find_control('publishalbum').items[0].selected = False - br.form.find_control('newalbum').items[0].selected = False + if album_name == None: + br.form.find_control('newalbum').items[0].selected = True + br.form.find_control('publishalbum').items[0].selected = True + br.form.find_control('existingfolder').value = "false" + br.form.find_control('folder').value = album_contents + else: + br.form.find_control('newalbum').items[0].selected = False + br.form.find_control('existingfolder').value = "true" + br.form.find_control('folder').value = album_name + br.form.find_control('autogenfolder').items[0].selected = False - br.form.find_control('existingfolder').value = "true" br.form.find_control('albumtitle').value = album_contents - br.form.find_control('folder').value = album_name + if sys.platform=="win32": br.form.find_control('files[]', nr=0).add_file(open(file, "rb"), "text/plain", file) else: @@ -88,51 +240,7 @@ print "Failure" else: print "Success" - - # open window to select the album & get result - - def select_album(self): - br = self.br - br.follow_link(text_regex=r"upload") - br.select_form(name="uploadform") - control = br.form.find_control('albumselect') - - # create an album selection - asel = self.wTree.get_widget("AlbumSelectDialog") - sd = self.wTree.get_widget("albums") - - if self.albums_populated == False: - self.albums_populated = True - m = gtk.ListStore(str) - for it in control.items: - m.append([it.attrs['contents']]) - sd.set_model(m) - cell = gtk.CellRendererText() - sd.pack_start(cell) - sd.add_attribute(cell,'text',0) - sd.set_active(0) - - self.ac_status = "quit" - asel.show() - result = asel.run() - asel.hide() - - if self.ac_status == "quit": - return None - - return self.ac_album - - # update the progress bar - - def progress_update(self): - pb = self.wTree.get_widget("progressbar") - if self.progress_status == -1: - pb.set_fraction(0) - else: - pb.set_text("%.0f%%" % (self.progress_status * 100)) - pb.set_fraction(self.progress_status) - return True - + # uploads a set of files to an album # runs on a dedicated thread so that the GUI does not get # unresponsive during the upload process. @@ -152,49 +260,191 @@ i = 0 for file in selections: - i = i + 1 - self.parent.progress_status = i / count - self.parent.upload_file(file, album) - self.parent.progress_status = -1 - # enable the upload button again - self.parent.wTree.get_widget("add_button").set_sensitive(True) - - # we are uploading. make sure the user has no chance - # to put more stuff to upload meanwhile. - self.wTree.get_widget("add_button").set_sensitive(False) + # Do not try to upload directories. Do each image separately + if os.path.isdir(file) == False: + i = i + 1 + self.parent.upload_file(file, album) + self.parent.progress.set_progress_status(i / count) + self.parent.progress.set_progress_status(-2) # Finished + ul = UploaderSlave() ul.set_parent(self) ul.set_files(selections) ul.set_album(album) ul.start() - # callback for the "add" button + def add_to_image_list(self, data): + ext_list = ["jpg", "jpeg", "JPG", "JPEG", "png", "bmp", "gif", "tiff", "raw"] + model = self.treeview.get_model() + for x in data: + filepath = x.strip() + if filepath[:7] == "file://": + filepath = filepath[7:] # remove file:// + + # drag and drop adds %20 for blank spaces, ... + # Special characters messed up + filepath = filepath.replace("%20", " ") + filepath = filepath.replace("%5B", "[") + filepath = filepath.replace("%5D", "]") + ext = os.path.splitext(x)[1][1:].strip() + + if ext in ext_list and os.path.exists(filepath): + filename = os.path.split(filepath)[1] + model.append([filename,filepath]) + # update the image count + self.update_image_count(1) + else: + print "Not added", filepath - def on_add_button_clicked(self, widget): - # select the album - album = self.select_album() - if album == None: - return + # callback for drag and drop + def drag_and_drop(self, w, drag_context, x, y, selection, info, time): + print "drag_and_drop" + + data = selection.data.split("\n") + self.add_to_image_list(data) + + + # callback for the "add" button + def on_add_photos_clicked(self, widget=None): + image_filter=gtk.FileFilter() + image_filter.set_name("Image files") + if sys.platform!="win32": + image_filter.add_mime_type("image/*") + else: + image_filter.add_custom(gtk.FILE_FILTER_FILENAME|gtk.FILE_FILTER_DISPLAY_NAME, custom_image_filter) + all_filter=gtk.FileFilter() + all_filter.set_name("All files") + all_filter.add_pattern("*") # select the files - dialog = gtk.FileSelection() + dialog = gtk.FileChooserDialog(title="Select Photos", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dialog.set_select_multiple(True) + dialog.add_filter(image_filter) + dialog.add_filter(all_filter) + result = dialog.run() if result == gtk.RESPONSE_OK: - selections = dialog.get_selections() - dialog.destroy() - self.upload(album, selections) + selections = dialog.get_filenames() else: - dialog.destroy() + pass + + dialog.destroy() + + self.add_to_image_list(selections) + + # callback for changed signal on treeview. All this means + # is that a different row was selected. + def on_treeview_image_list_changed(self, widget=None, event=None): + model, path = self.treeview_selection.get_selected_rows() + selected="" + for x in path: + selected = model[x[0]][1] # model[path][column] + print selected + break # only retrieve the first selected - # callback for the "connect" button - def on_connect_clicked(self, widget): + if sys.platform=="win32": + temp_dir = os.path.join(home,"Local Settings", "Temp") + else: + temp_dir = "/var/tmp" + + # set the preview image + #size = 120,160 + size = 200, 200 + try: + im = Image.open(selected) + im.thumbnail(size, Image.ANTIALIAS) + preview = os.path.join(temp_dir, "zpu_preview.jpg") + im.save(preview, "JPEG") + self.image_preview.set_from_file(preview) + except: + print "Preview Image not available" + + + # call back for removing images from the list + def on_remove_image_clicked(self, widget=None): + selection = self.treeview.get_selection() + model, iter = selection.get_selected() + if iter: + model.remove(iter) + self.update_image_count(-1) + return + + def on_connect_clicked(self, widget=None): + self.connect_zenphoto() + self.load_album_list() + + # menu connect button callback + def on_menu_connect_activate(self, widget=None): + self.connect_zenphoto() + self.load_album_list() + + def on_menu_help_activate(self, widget=None): + ZPAboutDialog() + + def on_menu_quit_activate(self, widget=None): + gtk.main_quit() + + def update_image_count(self, num): + self.image_count = self.image_count + num + self.image_count_label.set_text("Images: " + str(self.image_count)) + + def albums_changed(self, widget=None, event=None): + key = gtk.gdk.keyval_name(event.keyval) + if key == "Return": + self.albums.append_text(widget.get_text()) + widget.set_text("") + + # helper function for on_upload_clicked + def get_selected_album(self): + model = self.albums.get_model() + active = self.albums.get_active() + if active < 0: + return None + return model[active][0] + + # helper function for on_upload_clicked + def get_image_list(self): + file_list = [] + for x in self.liststore: + file_list.append(x[1]) # second column, full path to image + return file_list + + + + def on_upload_clicked(self, widget): + if self.connected == False: + message_dialog("Not connect to server. Please connect before uploading") + return False + + album = self.get_selected_album() + if album == None: + message_dialog("You must select a photo album") + return False + + + image_list = self.get_image_list() + + self.window.hide() + self.progress = ZPProgress(self.window) + gobject.timeout_add(100, self.progress.progress_update) + self.upload(album, image_list) + + def on_menu_preferences_activate(self, widget=None): + #self.preferences_window.show_all() + ZPPreferences() + + def set_status_text(self, text): + self.statusbar.pop(self.statusbar_context_id) + self.statusbar.push(self.statusbar_context_id, text) + + def connect_zenphoto(self): self.set_status_text("connecting..") - server = self.wTree.get_widget("server_text").get_text() - user = self.wTree.get_widget("user_text").get_text() - passwd = self.wTree.get_widget("pass_text").get_text() + + server = self.settings.get_server() + user = self.settings.get_user() + passwd = self.settings.get_password() try: br = Browser() @@ -207,17 +457,12 @@ if body.rfind('Logged in as') == -1: raise Exception() - self.wTree.get_widget("save_button").set_sensitive(True) - self.wTree.get_widget("add_button").set_sensitive(True) - self.wTree.get_widget("connect_button").set_sensitive(False) - self.wTree.get_widget("server_text").set_sensitive(False) - self.wTree.get_widget("user_text").set_sensitive(False) - self.wTree.get_widget("pass_text").set_sensitive(False) self.set_status_text("connected") self.server = server self.user = user self.passwd = passwd + self.connected = True except Exception: message = "Unable to connect to server admin zone (URL\n" + server \ @@ -231,108 +476,91 @@ result = dialog.run() dialog.destroy() self.set_status_text("disconnected") + self.connected = False self.br = br + - # get the name of the config file to initially load the config - # XXX portability is an issue - def get_config_file(self): - try: - homedir = "" - if sys.platform == 'win32': - homedir=os.environ["USERPROFILE"] - else: - homedir=os.environ["HOME"] - except Exception: - print "Error: home directory not found. Please set $HOME" - os._exit(-1) - config_path = os.path.join(homedir, '.zenphotouploader.rc') - if os.path.exists(config_path) == False: - new_file = open(config_path, "w") - new_file.write("") - new_file.close() - return os.path.join(homedir, '.zenphotouploader.rc') - - # try to load the config from a config file. - - def load_config(self): - - - server = '' - user = '' - password = '' - - configfile = self.get_config_file() - dict = {} - self.server = 'http://' - self.user = 'user' - self.passwd = 'pass' - - print "load config", configfile - try: - execfile(configfile, dict) - self.server = dict['server'] - self.user = dict['user'] - self.passwd = dict['password'] - except Exception, inst: - print "exc" - print Exception, inst - # save the current configuration. - - def save_config(self): - cfg = self.get_config_file() - f = open(cfg, "w") - f.write("server = '%s'\n" % self.server) - f.write("user = '%s'\n" % self.user) - f.write("password = '%s'\n" % self.passwd) - f.close() - pass + def load_album_list(self): + br = self.br + br.follow_link(text_regex=r"upload") + br.select_form(name="uploadform") + control = br.form.find_control('albumselect') + # combo entry widget + if self.albums_populated == False: + self.albums_populated = True + for x in control.items: + self.albums.append_text(x.attrs['contents']) + print x.attrs['contents'] - # init function. initializes the class' variables - # and brings up the main window - - def set_status_text(self, text): - pb = self.wTree.get_widget("progressbar") - pb.set_text(text) def __init__(self): + self.image_count = 0 + self.connected = False self.br = None self.albums_populated = False self.gladefile = "zpu.glade" - self.wTree = gtk.glade.XML(self.gladefile) + self.wTree = gtk.glade.XML(self.gladefile, "MainWindow") + self.wTree.signal_autoconnect(self) self.window = self.wTree.get_widget("MainWindow") - - self.wTree.signal_autoconnect({ - "destroy" : gtk.main_quit, - "on_quit_clicked" : gtk.main_quit, - "on_connect_clicked" : self.on_connect_clicked, - "on_save_button_clicked" : self.on_save_button_clicked, - "on_add_button_clicked" : self.on_add_button_clicked, - "on_MainWindow_destroy" : gtk.main_quit, - # album chooser - "on_cancel_clicked" : self.on_cancel_clicked, - "on_ok_clicked" : self.on_ok_clicked, - }) - - self.load_config() + self.albums = gtk.combo_box_entry_new_text() + album_box = self.wTree.get_widget("album_box") + album_box.pack_start(self.albums) + self.albums.child.connect('key-press-event', self.albums_changed) + #self.albums.child.connect('changed', self.albums_changed) + self.albums.show() + self.image_preview = self.wTree.get_widget("image_preview") + self.image_count_label = self.wTree.get_widget("label_image_count") - self.wTree.get_widget("server_text").set_text(self.server) - self.wTree.get_widget("user_text").set_text(self.user) - self.wTree.get_widget("pass_text").set_visibility(False) - self.wTree.get_widget("pass_text").set_text(self.passwd) + self.statusbar = self.wTree.get_widget("statusbar1") + self.statusbar_context_id = self.statusbar.get_context_id("display info") + + # Setup image list + self.liststore = gtk.ListStore(str, str) + #self.treeview = gtk.TreeView(self.liststore) + self.treeview = self.wTree.get_widget("treeview_image_list") + self.treeview_selection = self.treeview.get_selection() + self.treeview_selection.connect("changed", self.on_treeview_image_list_changed) + self.treeview.set_model(self.liststore) + # Add cell and column. + # data added to treeview. + self.cell = gtk.CellRendererText() + self.cell2 = gtk.CellRendererText() + # text=number is the column the text is displayed from + + + self.treeviewcolumn = gtk.TreeViewColumn("Image Name",self.cell, text=0) + self.treeviewcolumn2 = gtk.TreeViewColumn("Full Path", self.cell2, text=1) + self.treeviewcolumn2.set_visible(False) + self.treeview.append_column(self.treeviewcolumn) + self.treeview.append_column(self.treeviewcolumn2) + + + # Load config files + self.settings = ZPSettings() + self.server = self.settings.get_server() + self.user = self.settings.get_user() + self.passwd = self.settings.get_password() self.set_status_text("disconnected") + # drag and drop + self.window.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT | + gtk.DEST_DEFAULT_DROP, [("text/plain", 0, 80)], gtk.gdk.ACTION_COPY) + self.window.connect('drag_data_received', self.drag_and_drop) + + + self.window.connect("delete_event", lambda w,e: gtk.main_quit()) + self.progress_status = -1 - gobject.timeout_add(100, self.progress_update) - + if __name__ == "__main__": gtk.gdk.threads_init() - hwg = Uploader() + hwg = ZPUploader() gtk.gdk.threads_enter() gtk.main() gtk.gdk.threads_leave()