diff options
Diffstat (limited to 'rapid')
-rw-r--r-- | rapid/ChangeLog | 28 | ||||
-rw-r--r-- | rapid/config.py | 2 | ||||
-rw-r--r-- | rapid/glade3/about.ui.h | 1 | ||||
-rw-r--r-- | rapid/glade3/prefs.ui | 471 | ||||
-rw-r--r-- | rapid/glade3/prefs.ui.h | 99 | ||||
-rw-r--r-- | rapid/glade3/rapid.ui.h | 32 | ||||
-rw-r--r-- | rapid/preferencesdialog.py | 298 | ||||
-rw-r--r-- | rapid/prefsrapid.py | 37 | ||||
-rwxr-xr-x | rapid/rapid.py | 67 | ||||
-rwxr-xr-x | rapid/scan.py | 47 | ||||
-rw-r--r-- | rapid/subfolderfile.py | 9 |
11 files changed, 1024 insertions, 67 deletions
diff --git a/rapid/ChangeLog b/rapid/ChangeLog index 7c74bb9..06cdcf8 100644 --- a/rapid/ChangeLog +++ b/rapid/ChangeLog @@ -1,9 +1,35 @@ +Version 0.4.2 +------------- + +2011-08-xx + +Added feature in Prefences window to remove any paths that have previously +been marked to always be scanned or ignored. These paths can be specified when +automatic detection of Portable Storage Devices is enabled. + +Fixed bug #768026: added option to ignore paths from which to download. + +You can now specify specific paths never to scan for photos or videos. By +default, any path ending in .Trash or .thumbnails is ignored. Advanced users +can specify python-style regular expressions. + +Fixed bug #774488: added manual backup path for videos, in addition to photos. + +Fixed bug #789995: crash when --reset-settings option is given on the command +line. + +Fixed bug #795446: small error in translation template. + +Updated Brazilian, Dutch, Français, German, Japanese, Polish, Russian, Spanish +and Turkish translations. + + Version 0.4.1 ------------- 2011-05-19 -Added exif Artist and Copyright metadata options to file and subfoler name +Added exif Artist and Copyright metadata options to file and subfolder name generation. Fixed bug #774476: thumbnails occassionally not sorted by file modification diff --git a/rapid/config.py b/rapid/config.py index ce498c0..47ecfff 100644 --- a/rapid/config.py +++ b/rapid/config.py @@ -15,7 +15,7 @@ ### along with this program; if not, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -version = '0.4.1' +version = '0.4.2' GCONF_KEY="/apps/rapid-photo-downloader" diff --git a/rapid/glade3/about.ui.h b/rapid/glade3/about.ui.h new file mode 100644 index 0000000..7c721a5 --- /dev/null +++ b/rapid/glade3/about.ui.h @@ -0,0 +1 @@ +char *s = N_("Import your photos and videos efficiently and reliably"); diff --git a/rapid/glade3/prefs.ui b/rapid/glade3/prefs.ui index 8f75f62..d5ff1e0 100644 --- a/rapid/glade3/prefs.ui +++ b/rapid/glade3/prefs.ui @@ -1993,8 +1993,10 @@ <property name="can_focus">False</property> <property name="xalign">0</property> <property name="xpad">12</property> - <property name="label" translatable="yes"><b>Devices</b></property> - <property name="use_markup">True</property> + <property name="label" translatable="yes">Devices</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> </object> <packing> <property name="expand">False</property> @@ -2010,9 +2012,9 @@ <property name="xpad">12</property> <property name="label" translatable="yes">Devices are from where to download photos and videos, such as cameras, memory cards or Portable Storage Devices. -You can download photos from multiple devices simultaneously, or you can specify a location on your hard drive. +You can download from multiple devices simultaneously, or you can specify a location on your hard drive. -<i>If downloading directly from your camera works poorly or not at all, try setting it to PTP mode. If that is not possible, consider using a card reader.</i></property> +<i>Downloading directly from cameras is currently an experimental feature. If downloading directly from your camera works poorly or not at all, try setting it to PTP mode. If that is not possible, a card reader must be used.</i></property> <property name="use_markup">True</property> <property name="wrap">True</property> </object> @@ -2083,7 +2085,9 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="can_focus">False</property> <property name="xalign">0</property> <property name="ypad">6</property> - <property name="label" translatable="yes">If you enable automatic detection of Portable Storage Devices, the entire device will be scanned for images. On large devices, this could take some time.</property> + <property name="label" translatable="yes">If you enable automatic detection of Portable Storage Devices, the entire device will be scanned. On large devices, this could take some time. + +When this option is enabled, and a potential device is detected, you will be prompted to determine if it should be scanned or not.</property> <property name="wrap">True</property> </object> <packing> @@ -2158,7 +2162,7 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="xalign">0</property> <property name="xpad">12</property> <property name="ypad">12</property> - <property name="label" translatable="yes">If you disable automatic detection, choose the exact location of the images and videos.</property> + <property name="label" translatable="yes">If you disable automatic detection, choose the exact location of the photos and videos.</property> <property name="wrap">True</property> </object> <packing> @@ -2221,6 +2225,420 @@ You can download photos from multiple devices simultaneously, or you can specify </packing> </child> <child> + <object class="GtkVBox" id="device_options_tab"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="exclusions_header_hbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage" id="image10"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-preferences</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="exclusionlabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes"><span weight="bold" size="x-large">Device Options</span></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHSeparator" id="hseparator3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkVBox" id="vbox15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="remebered_devices_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="xpad">12</property> + <property name="label" translatable="yes">Remembered Paths</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="remembered_devices_explanation_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="xpad">12</property> + <property name="label" translatable="yes">Remembered paths are those that you have chosen to always scan or ignore when automatic detection of Portable Storage Devices is enabled.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="remembered_devices_hbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="remembered_devices_spacer_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="remembered_devices_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTreeView" id="remembered_devices_treeview"> + <property name="width_request">250</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="remembered_devices_button_hbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVButtonBox" id="remembered_devices_vbuttonbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <property name="layout_style">start</property> + <child> + <object class="GtkButton" id="remove_remembered_device_button"> + <property name="label">gtk-remove</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_remove_remembered_device_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="remove_all_remembered_device_button"> + <property name="label" translatable="yes" comments="The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c'">R_emove All</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_remove_all_remembered_device_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">12</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="ignored_paths_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="xpad">12</property> + <property name="label" translatable="yes">Ignored Paths</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="ignored_paths_explanation_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="xpad">12</property> + <property name="label" translatable="yes">Specify any paths you want ignored when scanning devices for photos or videos. Any path ending with the values below will not be scanned.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="ignored_paths_hbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="ignored_paths_spacer_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="ignored_paths_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTreeView" id="ignored_paths_treeview"> + <property name="width_request">250</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="ignored_paths_button_hbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVButtonBox" id="ignored_paths_vbuttonbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <property name="layout_style">start</property> + <child> + <object class="GtkButton" id="add_ignored_path_button"> + <property name="label" translatable="yes">_Add...</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_add_ignored_path_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="remove_ignored_path_button"> + <property name="label">gtk-remove</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_remove_ignored_path_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="remove_all_ignored_paths_button"> + <property name="label" translatable="yes" comments="The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c'">Re_move All</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_remove_all_ignored_paths_button_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkCheckButton" id="ignored_paths_use_re_checkbutton"> + <property name="label" translatable="yes">Use _python-style regular expressions</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="on_ignored_paths_use_re_checkbutton_toggled" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">12</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="position">7</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="device_options_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Device Options</property> + </object> + <packing> + <property name="position">7</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> <object class="GtkVBox" id="backup_tab"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -2321,7 +2739,7 @@ You can download photos from multiple devices simultaneously, or you can specify <object class="GtkTable" id="backup_table"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="n_rows">9</property> + <property name="n_rows">10</property> <property name="n_columns">4</property> <child> <object class="GtkLabel" id="backup_location_explanation_label"> @@ -2330,7 +2748,7 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> <property name="ypad">12</property> - <property name="label" translatable="yes">If you disable automatic detection, choose the exact backup location.</property> + <property name="label" translatable="yes">If you disable automatic detection, choose the exact backup locations.</property> <property name="wrap">True</property> </object> <packing> @@ -2338,6 +2756,7 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="right_attach">4</property> <property name="top_attach">7</property> <property name="bottom_attach">8</property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> @@ -2415,7 +2834,7 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="visible">True</property> <property name="can_focus">False</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Backup location:</property> + <property name="label" translatable="yes">Photo backup location:</property> </object> <packing> <property name="left_attach">1</property> @@ -2538,6 +2957,28 @@ You can download photos from multiple devices simultaneously, or you can specify </packing> </child> <child> + <object class="GtkLabel" id="video_backup_location_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Video backup location:</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">3</property> + <property name="top_attach">9</property> + <property name="bottom_attach">10</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> <placeholder/> </child> <child> @@ -2608,7 +3049,7 @@ You can download photos from multiple devices simultaneously, or you can specify </child> </object> <packing> - <property name="position">7</property> + <property name="position">8</property> </packing> </child> <child type="tab"> @@ -2618,7 +3059,7 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="label" translatable="yes">Backup</property> </object> <packing> - <property name="position">7</property> + <property name="position">8</property> <property name="tab_fill">False</property> </packing> </child> @@ -2956,7 +3397,7 @@ You can download photos from multiple devices simultaneously, or you can specify </child> </object> <packing> - <property name="position">8</property> + <property name="position">9</property> </packing> </child> <child type="tab"> @@ -2967,7 +3408,7 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="label" translatable="yes">Miscellaneous</property> </object> <packing> - <property name="position">8</property> + <property name="position">9</property> <property name="tab_fill">False</property> </packing> </child> @@ -3278,7 +3719,7 @@ You can download photos from multiple devices simultaneously, or you can specify </child> </object> <packing> - <property name="position">9</property> + <property name="position">10</property> </packing> </child> <child type="tab"> @@ -3289,7 +3730,7 @@ You can download photos from multiple devices simultaneously, or you can specify <property name="label" translatable="yes">Error Handling</property> </object> <packing> - <property name="position">9</property> + <property name="position">10</property> <property name="tab_fill">False</property> </packing> </child> diff --git a/rapid/glade3/prefs.ui.h b/rapid/glade3/prefs.ui.h new file mode 100644 index 0000000..ef85be3 --- /dev/null +++ b/rapid/glade3/prefs.ui.h @@ -0,0 +1,99 @@ +char *s = N_(" "); +char *s = N_(" hh:mm"); +char *s = N_(":"); +char *s = N_("<b>Backup</b>"); +char *s = N_("<b>Compatibility with Other Operating Systems</b>"); +char *s = N_("<b>Download Folder</b>"); +char *s = N_("<b>Download Subfolders</b>"); +char *s = N_("<b>Example</b>"); +char *s = N_("<b>Job Codes</b>"); +char *s = N_("<b>Photo Rename</b>"); +char *s = N_("<b>Photo and Video Name Conflicts</b>"); +char *s = N_("<b>Program Automation</b>"); +char *s = N_("<b>Sequence Numbers</b>"); +char *s = N_("<i>/media/externaldrive/Photos</i>"); +char *s = N_("<i>Example: /home/user/Pictures</i>"); +char *s = N_("<i>Example:</i>"); +char *s = N_("<i>New:</i>"); +char *s = N_("<i>Original:</i>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Backup</span> "); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Device Options</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Devices</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Error Handling</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Job Codes</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Miscellaneous</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Photo Download Folders</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Photo Rename</span> "); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Rename Options</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Video Download Folders</span>"); +char *s = N_("<span weight=\"bold\" size=\"x-large\">Video Rename</span> "); +char *s = N_("Add unique identifier"); +char *s = N_("Automatically detect Portable Storage Devices"); +char *s = N_("Automatically detect backup devices"); +char *s = N_("Automatically detect devices"); +char *s = N_("Backup"); +char *s = N_("Backup photos and videos when downloading"); +char *s = N_("Choose the download folder. Subfolders for the downloaded photos will be automatically created in this folder using the structure specified below."); +char *s = N_("Choose the download folder. Subfolders for the downloaded videos will be automatically created in this folder using the structure specified below."); +char *s = N_("Day start:"); +char *s = N_("Delete photos and videos from device upon download completion"); +char *s = N_("Device Options"); +char *s = N_("Devices"); +char *s = N_("Devices are from where to download photos and videos, such as cameras, memory cards or Portable Storage Devices.\n" + "\n" + "You can download from multiple devices simultaneously, or you can specify a location on your hard drive.\n" + "\n" + "<i>Downloading directly from cameras is currently an experimental feature. If downloading directly from your camera works poorly or not at all, try setting it to PTP mode. If that is not possible, a card reader must be used.</i>"); +char *s = N_("Download folder:"); +char *s = N_("Downloads today:"); +char *s = N_("Error Handling"); +char *s = N_("Exit program even if download had warnings or errors"); +char *s = N_("Exit program when download completes"); +char *s = N_("Generate thumbnails (slower)"); +char *s = N_("If you disable automatic detection, choose the exact backup locations."); +char *s = N_("If you disable automatic detection, choose the exact location of the photos and videos."); +char *s = N_("If you enable automatic detection of Portable Storage Devices, the entire device will be scanned. On large devices, this could take some time.\n" + "\n" + "When this option is enabled, and a potential device is detected, you will be prompted to determine if it should be scanned or not."); +char *s = N_("Ignored Paths"); +char *s = N_("Job Codes"); +char *s = N_("Location:"); +char *s = N_("Miscellaneous"); +char *s = N_("Overwrite"); +char *s = N_("Performance"); +char *s = N_("Photo Folders"); +char *s = N_("Photo Rename"); +char *s = N_("Photo backup folder name:"); +char *s = N_("Photo backup location:"); +char *s = N_("Preferences: Rapid Photo Downloader"); +/* The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c' */ +char *s = N_("R_emove All"); +/* The underscore after the C signifies that the l is the accelerator key. This is the standard 'Clear' button, but I needed to change the accelerator from the standard 'c' to 'l' because the close button also used 'c' */ +char *s = N_("Re_move All"); +char *s = N_("Remembered Paths"); +char *s = N_("Remembered paths are those that you have chosen to always scan or ignore when automatic detection of Portable Storage Devices is enabled."); +char *s = N_("Rename Options"); +char *s = N_("Skip"); +char *s = N_("Skip download"); +char *s = N_("Sorry, video downloading functionality disabled. To download videos, please install the <i>hachoir metadata</i> and <i>kaa metadata</i> packages for python."); +char *s = N_("Specify any paths you want ignored when scanning devices for photos or videos. Any path ending with the values below will not be scanned."); +char *s = N_("Specify the folder in which backups are stored on the device. \n" + "\n" + "<i>Note: this will also be used to determine whether or not the device is used for backups. For each device you wish to use for backing up to, create a folder in it with one of these names.</i>"); +char *s = N_("Specify the time in 24 hour format at which the <i>Downloads today</i> sequence number should be reset."); +char *s = N_("Specify whether photo, video and folder names should have any characters removed that are not allowed by other operating systems."); +char *s = N_("Start downloading at program startup"); +char *s = N_("Start downloading upon device insertion"); +char *s = N_("Stored number:"); +char *s = N_("Strip incompatible characters"); +char *s = N_("Synchronize RAW + JPEG sequence numbers"); +char *s = N_("Unmount (\"eject\") device upon download completion"); +char *s = N_("Use _python-style regular expressions"); +char *s = N_("Video Folders"); +char *s = N_("Video Rename"); +char *s = N_("Video backup folder name:"); +char *s = N_("Video backup location:"); +char *s = N_("When a photo or video of the same name has already been downloaded, choose whether to skip downloading the file, or to add a unique indentifier."); +char *s = N_("When backing up, choose whether to overwrite a file on the backup device that has the same name, or skip backing it up."); +char *s = N_("You can have your photos and videos backed up to multiple locations as they are downloaded, e.g. external hard drives."); +char *s = N_("_Add..."); diff --git a/rapid/glade3/rapid.ui.h b/rapid/glade3/rapid.ui.h new file mode 100644 index 0000000..7d5efa3 --- /dev/null +++ b/rapid/glade3/rapid.ui.h @@ -0,0 +1,32 @@ +char *s = N_("About..."); +char *s = N_("Check All"); +char *s = N_("Check All Photos"); +char *s = N_("Check All Videos"); +char *s = N_("Download"); +char *s = N_("Download / Pause"); +char *s = N_("Get Help Online..."); +char *s = N_("Help"); +char *s = N_("Make a Donation..."); +char *s = N_("Next File"); +char *s = N_("Preferences"); +char *s = N_("Previous File"); +char *s = N_("Quit"); +char *s = N_("Rapid Photo Downloader"); +char *s = N_("Refresh"); +char *s = N_("Report a Problem..."); +char *s = N_("Select All Wit_h Job Code"); +char *s = N_("Select All Without _Job Code"); +char *s = N_("Translate this Application..."); +char *s = N_("Uncheck All"); +char *s = N_("_Check All"); +char *s = N_("_Clear Completed Downloads"); +char *s = N_("_Download"); +char *s = N_("_Error Log"); +char *s = N_("_File"); +char *s = N_("_Help"); +char *s = N_("_Include in download"); +char *s = N_("_Make a Donation..."); +char *s = N_("_Select"); +char *s = N_("_Translate this Application..."); +char *s = N_("_Uncheck All"); +char *s = N_("_View"); diff --git a/rapid/preferencesdialog.py b/rapid/preferencesdialog.py index 7289d0e..ea66c8d 100644 --- a/rapid/preferencesdialog.py +++ b/rapid/preferencesdialog.py @@ -18,7 +18,7 @@ ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import datetime +import datetime, re import gtk @@ -384,9 +384,9 @@ class VideoSubfolderPrefs(PhotoSubfolderPrefs): pref_defn_L0 = DICT_VIDEO_SUBFOLDER_L0, pref_list = pref_list) -class RemoveAllJobCodeDialog(gtk.Dialog): - def __init__(self, parent_window, post_choice_callback): - gtk.Dialog.__init__(self, _('Remove all Job Codes?'), None, +class QuestionDialog(gtk.Dialog): + def __init__(self, parent_window, title, question, post_choice_callback): + gtk.Dialog.__init__(self, title, None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_NO, gtk.RESPONSE_CANCEL, gtk.STOCK_YES, gtk.RESPONSE_OK)) @@ -403,7 +403,7 @@ class RemoveAllJobCodeDialog(gtk.Dialog): image.set_from_pixbuf(icon) prompt_hbox.pack_start(image, False, False, padding = 6) - prompt_label = gtk.Label(_('Should all Job Codes be removed?')) + prompt_label = gtk.Label(question) prompt_label.set_line_wrap(True) prompt_hbox.pack_start(prompt_label, False, False, padding=6) @@ -420,11 +420,32 @@ class RemoveAllJobCodeDialog(gtk.Dialog): self.connect('response', self.on_response) - + def on_response(self, device_dialog, response): user_selected = response == gtk.RESPONSE_OK self.post_choice_callback(self, user_selected) +class RemoveAllJobCodeDialog(QuestionDialog): + def __init__(self, parent_window, post_choice_callback): + QuestionDialog.__init__(self, parent_window, + _('Remove all Job Codes?'), + _('Should all Job Codes be removed?'), + post_choice_callback) + +class RemoveAllRemeberedDevicesDialog(QuestionDialog): + def __init__(self, parent_window, post_choice_callback): + QuestionDialog.__init__(self, parent_window, + _('Remove all Remembered Devices?'), + _('Should all remembered devices be removed?'), + post_choice_callback) + +class RemoveAllIgnoredPathsDialog(QuestionDialog): + def __init__(self, parent_window, post_choice_callback): + QuestionDialog.__init__(self, parent_window, + _('Remove all Ignored Paths?'), + _('Should all ignored paths be removed?'), + post_choice_callback) + class PhotoRenameTable(tpm.TablePlusMinus): def __init__(self, preferencesdialog, adjust_scroll_window): @@ -716,7 +737,6 @@ class JobCodeDialog(gtk.Dialog): (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) - self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg')) self.post_job_code_entry_callback = post_job_code_entry_callback @@ -788,6 +808,54 @@ class JobCodeDialog(gtk.Dialog): logger.debug("Job Code not entered") self.post_job_code_entry_callback(self, user_chose_code, self.get_job_code()) +class IgnorePathDialog(gtk.Dialog): + """ Dialog prompting for a path to ignore when scanning devices""" + + def __init__(self, parent_window, post_entry_callback): + gtk.Dialog.__init__(self, _('Enter a Path to Ignore'), None, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OK, gtk.RESPONSE_OK)) + + self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg')) + self.post_entry_callback = post_entry_callback + + self.path_entry = gtk.Entry(max=0) + + self.ignored_path_hbox = gtk.HBox(homogeneous = False) + + task_label = gtk.Label(_('Specify a path that will never be scanned for photos or videos')) + task_label.set_line_wrap(True) + task_hbox = gtk.HBox() + task_hbox.pack_start(task_label, False, False, padding=6) + + label = gtk.Label(_('Path:')) + self.ignored_path_hbox.pack_start(label, False, False, padding=6) + self.ignored_path_hbox.pack_start(self.path_entry, True, True, padding=6) + + self.set_border_width(6) + self.set_has_separator(False) + + # when user hits enter, close the dialog window + self.set_default_response(gtk.RESPONSE_OK) + self.path_entry.set_activates_default(True) + + self.vbox.pack_start(task_hbox, False, False, padding = 6) + self.vbox.pack_start(self.ignored_path_hbox, False, False, padding=12) + + self.set_transient_for(parent_window) + self.show_all() + self.connect('response', self.on_ignored_path_resp) + + def on_ignored_path_resp(self, ignored_path_dialog, response): + user_chose_path = False + if response == gtk.RESPONSE_OK: + user_chose_path = True + logger.debug("Ignored Path entered") + else: + logger.debug("Ignored Path not entered") + self.post_entry_callback(self, user_chose_path, self.path_entry.get_text()) + class PreferencesDialog(): """ @@ -834,6 +902,7 @@ class PreferencesDialog(): self._setup_rename_options_tab() self._setup_job_code_tab() self._setup_device_tab() + self._setup_device_options_tab() self._setup_backup_tab() self._setup_miscellaneous_tab() self._setup_error_tab() @@ -905,6 +974,95 @@ class PreferencesDialog(): def on_device_location_filechooser_button_selection_changed(self, widget): self.prefs.device_location = widget.get_current_folder() + + def on_add_ignored_path_button_clicked(self, widget): + i = IgnorePathDialog(parent_window = self.dialog, + post_entry_callback = self.add_ignored_path) + + def add_ignored_path(self, dialog, user_chose_path, path): + dialog.destroy() + if user_chose_path: + if path and path not in self.prefs.ignored_paths: + self.ignored_paths_liststore.prepend((path, )) + self.update_ignored_paths() + selection = self.ignored_paths_treeview.get_selection() + selection.unselect_all() + selection.select_path((0, )) + #scroll to the top + adjustment = self.ignored_paths_scrolledwindow.get_vadjustment() + adjustment.set_value(adjustment.lower) + + def on_ignored_paths_use_re_checkbutton_toggled(self, checkbutton): + self.prefs.use_re_ignored_paths = checkbutton.get_active() + if self.prefs.use_re_ignored_paths and not self.pref_dialog_startup: + # check for invalid regular expressions + self.update_ignored_paths() + + def on_remove_ignored_path_button_clicked(self, button): + self._remove_from_treeview(self.ignored_paths_treeview) + self.update_ignored_paths() + + def on_remove_all_ignored_paths_button_clicked(self, button): + i = RemoveAllIgnoredPathsDialog(self.dialog, self.remove_all_ignored_paths) + + def remove_all_ignored_paths(self, dialog, user_selected): + dialog.destroy() + if user_selected: + self.ignored_paths_liststore.clear() + self.update_ignored_paths() + + def on_remove_remembered_device_button_clicked(self, button): + """ + uses remembered devices treeview to delete any removed items from the + device_whitelist and device_blacklist prefs + """ + blacklist = [i for i in self.prefs.device_blacklist if i] + whitelist = [i for i in self.prefs.device_whitelist if i] + selection = self.remembered_devices_treeview.get_selection() + model, selected = selection.get_selected_rows() + iters = [model.get_iter(path) for path in selected] + # only delete if a value is selected + if iters: + no = len(iters) + path = None + for i in range(0, no): + iter = iters[i] + if i == no - 1: + path = model.get_path(iter) + v = self.remembered_devices_liststore.get_value(iter, 0) + if v in blacklist: + blacklist.remove(v) + elif v in whitelist: + whitelist.remove(v) + else: + logger.debug("Unknown remembered device %s", v) + model.remove(iter) + + # now that we removed the selection, play nice with + # the user and select the next item + selection.select_path(path) + + # if there was no selection that meant the user + # removed the last entry, so we try to select the + # last item + if not selection.path_is_selected(path): + row = path[0]-1 + # test case for empty lists + if row >= 0: + selection.select_path((row,)) + + self.prefs.device_blacklist = blacklist + self.prefs.device_whitelist = whitelist + + def on_remove_all_remembered_device_button_clicked(self, button): + r = RemoveAllRemeberedDevicesDialog(self.dialog, self.remove_all_remembered_devices) + + def remove_all_remembered_devices(self, dialog, user_selected): + dialog.destroy() + if user_selected: + self.remembered_devices_liststore.clear() + self.prefs.device_blacklist = [] + self.prefs.device_whitelist = [] def _setup_sample_names(self, use_dummy_data = False): """ @@ -1085,7 +1243,7 @@ class PreferencesDialog(): column = gtk.TreeViewColumn() rentext = gtk.CellRendererText() rentext.connect('edited', self.on_job_code_edited) - rentext .set_property('editable', True) + rentext.set_property('editable', True) column.pack_start(rentext, expand=0) column.set_attributes(rentext, text=0) @@ -1101,6 +1259,60 @@ class PreferencesDialog(): self.remove_all_job_code_button.set_image(gtk.image_new_from_stock( gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON)) + def _setup_device_options_tab(self): + """ + Setup ignored paths and remembered devices tab in prefs dialog + """ + + self.ignored_paths_use_re_checkbutton.set_active( + self.prefs.use_re_ignored_paths) + + self.ignored_paths_liststore = gtk.ListStore(str) + column = gtk.TreeViewColumn() + rentext = gtk.CellRendererText() + rentext.connect('edited', self.on_ignored_path_edited) + rentext.set_property('editable', True) + + column.pack_start(rentext, expand=0) + column.set_attributes(rentext, text=0) + self.ignored_paths_treeview_column = column + self.ignored_paths_treeview.append_column(column) + self.ignored_paths_treeview.props.model = self.ignored_paths_liststore + for path in self.prefs.ignored_paths: + self.ignored_paths_liststore.append((path, )) + + self.ignored_paths_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + self.remove_all_ignored_paths_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_CLEAR, + gtk.ICON_SIZE_BUTTON)) + + # Remembered devices are a little different in that they cannot be + # edited, and they can only added when the user is prompted by the + # program. Moreover, the list the user sees is a combination of two + # lists: device_whitelist and device_blacklist + + self.remembered_devices_liststore = gtk.ListStore(str) + column = gtk.TreeViewColumn() + rentext = gtk.CellRendererText() + rentext.set_property('editable', False) + + column.pack_start(rentext, expand=0) + column.set_attributes(rentext, text=0) + self.remembered_devices_treeview_column = column + self.remembered_devices_treeview.append_column(column) + self.remembered_devices_treeview.props.model = self.remembered_devices_liststore + for device in self.prefs.device_whitelist: + if device: + self.remembered_devices_liststore.append((device, )) + for device in self.prefs.device_blacklist: + if device: + self.remembered_devices_liststore.append((device, )) + + self.remembered_devices_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + self.remove_all_remembered_device_button.set_image(gtk.image_new_from_stock( + gtk.STOCK_CLEAR, + gtk.ICON_SIZE_BUTTON)) + def _setup_device_tab(self): self.device_location_filechooser_button = gtk.FileChooserButton( @@ -1400,12 +1612,14 @@ class PreferencesDialog(): adjustment = self.job_code_scrolledwindow.get_vadjustment() adjustment.set_value(adjustment.lower) - def on_remove_job_code_button_clicked(self, button): - """ remove selected job codes (can be multiple selection)""" - selection = self.job_code_treeview.get_selection() + def _remove_from_treeview(self, treeview): + """ + Removes selected items from a treeview, allowing multiple selections + """ + selection = treeview.get_selection() model, selected = selection.get_selected_rows() iters = [model.get_iter(path) for path in selected] - # only delete if a jobe code is selected + # only delete if a value is selected if iters: no = len(iters) path = None @@ -1419,7 +1633,7 @@ class PreferencesDialog(): # the user and select the next item selection.select_path(path) - # if there was no selection that meant the user + # if there was no selection that meant the user # removed the last entry, so we try to select the # last item if not selection.path_is_selected(path): @@ -1427,14 +1641,19 @@ class PreferencesDialog(): # test case for empty lists if row >= 0: selection.select_path((row,)) - + + + def on_remove_job_code_button_clicked(self, button): + """ remove selected job codes (can be multiple selection)""" + + self._remove_from_treeview(self.job_code_treeview) self.update_job_codes() self.update_photo_rename_example() self.update_video_rename_example() self.update_photo_download_folder_example() self.update_video_download_folder_example() - def on_remove_all_job_code_button_clicked(self, button): + def on_remove_all_job_code_button_clicked(self, button): j = RemoveAllJobCodeDialog(self.dialog, self.remove_all_job_code) def remove_all_job_code(self, dialog, user_selected): @@ -1456,13 +1675,51 @@ class PreferencesDialog(): self.update_photo_download_folder_example() self.update_video_download_folder_example() + def _update_prefs_list(self, liststore): + replacement_list = [] + for row in liststore: + replacement_list.append(row[0]) + return replacement_list + def update_job_codes(self): """ update preferences with list of job codes""" - job_codes = [] - for row in self.job_code_liststore: - job_codes.append(row[0]) - self.prefs.job_codes = job_codes - + self.prefs.job_codes = self._update_prefs_list(self.job_code_liststore) + + def on_ignored_path_edited(self, widget, path, new_text): + iter = self.ignored_paths_liststore.get_iter(path) + self.ignored_paths_liststore.set_value(iter, 0, new_text) + self.update_ignored_paths() + + def update_ignored_paths(self): + ignored_paths = self._update_prefs_list(self.ignored_paths_liststore) + + # remove any trailing slashes + ignored_paths = [path.rstrip('/') for path in ignored_paths if path] + # remove any blank values from ignored_paths + ignored_paths = [path for path in ignored_paths if path] + + if self.prefs.use_re_ignored_paths: + ip = [] + bad_paths = '' + for path in ignored_paths: + # check for validity + try: + re.match(path, '') + ip.append(path) + except: + logger.error("Ignoring invalid regular expression: %s", path) + bad_paths += path + '\n' + ignored_paths = ip + if bad_paths: + bad_paths = bad_paths[:-1] + if bad_paths.find('\n') >= 0: + msg = _("The following regular expressions are invalid, and will be removed unless you correct them:\n %s") % bad_paths + else: + msg = _("This regular expression is invalid, and will be removed unless you correct it:\n %s") % bad_paths + misc.run_dialog(_("Invalid regular expression"), msg, self) + + self.prefs.ignored_paths = ignored_paths + def on_auto_startup_checkbutton_toggled(self, checkbutton): self.prefs.auto_download_at_startup = checkbutton.get_active() @@ -1471,7 +1728,6 @@ class PreferencesDialog(): def on_auto_unmount_checkbutton_toggled(self, checkbutton): self.prefs.auto_unmount = checkbutton.get_active() - def on_auto_delete_checkbutton_toggled(self, checkbutton): self.prefs.auto_delete = checkbutton.get_active() diff --git a/rapid/prefsrapid.py b/rapid/prefsrapid.py index f13530f..6fa5f74 100644 --- a/rapid/prefsrapid.py +++ b/rapid/prefsrapid.py @@ -17,7 +17,7 @@ ### along with this program; if not, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import subprocess, os, datetime +import subprocess, os, datetime, re import prefs @@ -91,6 +91,9 @@ class RapidPreferences(prefs.Preferences): "device_autodetection_psd": prefs.Value(prefs.BOOL, False), "device_whitelist": prefs.ListValue(prefs.STRING_LIST, ['']), "device_blacklist": prefs.ListValue(prefs.STRING_LIST, ['']), + "ignored_paths": prefs.ListValue(prefs.STRING_LIST, ['.Trash', + '.thumbnails']), + "use_re_ignored_paths": prefs.Value(prefs.BOOL, False), "backup_images": prefs.Value(prefs.BOOL, False), "backup_device_autodetection": prefs.Value(prefs.BOOL, True), "backup_identifier": prefs.Value(prefs.STRING, @@ -412,5 +415,33 @@ def format_pref_list_for_pretty_print(pref_list): s = "%s (%s)" % (s, pref_list[i+2]) v += s + "\n" return v - - + + +def check_and_compile_re(ignored_paths): + """ + configure regular expression to search for, checking to see it is valid + + returns compiled RE, or None if invalid + """ + + pattern = '' + for path in ignored_paths: + # check for validity + try: + re.match(path, '') + pattern += '.*%s$|' % path + except: + logger.error("Ignoring invalid regular expression: %s", path) + if pattern: + pattern = pattern[:-1] + + logger.debug("Ignored paths regular expression pattern: %s", pattern) + if not pattern: + logger.warning("No regular expression is specified") + return None + else: + try: + return re.compile(pattern) + except: + logger.error('This regular expression is invalid: %s', pattern) + return None diff --git a/rapid/rapid.py b/rapid/rapid.py index ce4fa00..237c843 100755 --- a/rapid/rapid.py +++ b/rapid/rapid.py @@ -1150,16 +1150,22 @@ class TaskManager: class ScanManager(TaskManager): - def __init__(self, results_callback, batch_size, generate_folder, + def __init__(self, results_callback, batch_size, add_device_function): TaskManager.__init__(self, results_callback, batch_size) self.add_device_function = add_device_function - self.generate_folder = generate_folder - def _initiate_task(self, device, task_results_conn, task_process_conn, + def _initiate_task(self, task, task_results_conn, task_process_conn, terminate_queue, run_event): - - scan = scan_process.Scan(device.get_path(), self.batch_size, self.generate_folder, + + device = task[0] + ignored_paths = task[1] + use_re_ignored_paths = task[2] + + scan = scan_process.Scan(device.get_path(), + ignored_paths, + use_re_ignored_paths, + self.batch_size, task_process_conn, terminate_queue, run_event) scan.start() self._processes.append((scan, terminate_queue, run_event)) @@ -1289,9 +1295,10 @@ class SubfolderFileManager(SingleInstanceTaskManager): """ Manages the daemon process that renames files and creates subfolders """ - def __init__(self, results_callback, sequence_values): + def __init__(self, results_callback, sequence_values, focal_length): SingleInstanceTaskManager.__init__(self, results_callback) - self._subfolder_file = subfolderfile.SubfolderFile(self.task_process_conn, sequence_values) + self._subfolder_file = subfolderfile.SubfolderFile(self.task_process_conn, + sequence_values, focal_length) self._subfolder_file.start() logger.debug("SubfolderFile PID: %s", self._subfolder_file.pid) @@ -1427,13 +1434,15 @@ class RapidApp(dbus.service.Object): processes. """ - def __init__(self, bus, path, name, taskserver=None): + def __init__(self, bus, path, name, taskserver=None, focal_length=None): dbus.service.Object.__init__ (self, bus, path, name) self.running = False self.taskserver = taskserver + self.focal_length = focal_length + # Setup program preferences, and set callback for when they change self._init_prefs() @@ -1640,6 +1649,15 @@ class RapidApp(dbus.service.Object): name = self.backup_devices[path].get_name() return name + def start_device_scan(self, device): + """ + Commences the scanning of a device using the preference values for + any paths to ignore while scanning + """ + return self.scan_manager.add_task([device, + self.prefs.ignored_paths, + self.prefs.use_re_ignored_paths]) + def setup_devices(self, on_startup, on_preference_change, block_auto_start): """ @@ -1687,7 +1705,10 @@ class RapidApp(dbus.service.Object): elif (self.prefs.device_autodetection and (dv.is_DCIM_device(path) or self.search_for_PSD())): + logger.debug("Appending %s", mount.get_name()) mounts.append((path, mount)) + else: + logger.debug("Ignoring %s", mount.get_name()) if not self.prefs.device_autodetection: @@ -1733,7 +1754,7 @@ class RapidApp(dbus.service.Object): # prompt user to see if device should be used or not self.get_use_device(device) else: - scan_pid = self.scan_manager.add_task(device) + scan_pid = self.start_device_scan(device) if mount is not None: self.mounts_by_path[path] = scan_pid if not mounts: @@ -1759,7 +1780,7 @@ class RapidApp(dbus.service.Object): self.prefs.device_whitelist = self.prefs.device_whitelist + [path] else: self.prefs.device_whitelist = [path] - scan_pid = self.scan_manager.add_task(device) + scan_pid = self.start_device_scan(device) self.mounts_by_path[path] = scan_pid elif permanent_choice and path not in self.prefs.device_blacklist: @@ -1878,7 +1899,7 @@ class RapidApp(dbus.service.Object): # prompt user if device should be used or not self.get_use_device(device) else: - scan_pid = self.scan_manager.add_task(device) + scan_pid = self.start_device_scan(device) self.mounts_by_path[path] = scan_pid def on_mount_removed(self, vmonitor, mount): @@ -2663,7 +2684,9 @@ class RapidApp(dbus.service.Object): if key == 'show_log_dialog': self.menu_log_window.set_active(value) - elif key in ['device_autodetection', 'device_autodetection_psd', 'device_location']: + elif key in ['device_autodetection', 'device_autodetection_psd', + 'device_location', 'ignored_paths', + 'use_re_ignored_paths', 'device_blacklist']: self.rerun_setup_available_image_and_video_media = True if not self.preferences_dialog_displayed: self.post_preference_change() @@ -3223,12 +3246,12 @@ class RapidApp(dbus.service.Object): self.subfolder_file_manager = SubfolderFileManager( self.subfolder_file_results, - sequence_values) + sequence_values, + self.focal_length) - self.generate_folder = False self.scan_manager = ScanManager(self.scan_results, self.batch_size, - self.generate_folder, self.device_collection.add_device) + self.device_collection.add_device) self.copy_files_manager = CopyFilesManager(self.copy_files_results, self.batch_size_MB) self.backup_manager = BackupFilesManager(self.backup_results, @@ -3310,6 +3333,7 @@ def start(): parser.add_option("-q", "--quiet", action="store_false", dest="verbose", help=_("only output errors to the command line")) # image file extensions are recognized RAW files plus TIFF and JPG parser.add_option("-e", "--extensions", action="store_true", dest="extensions", help=_("list photo and video file extensions the program recognizes and exit")) + parser.add_option("--focal-length", type=int, dest="focal_length", help="If an aperture value of 0.0 is encountered, for file renaming purposes the metadata for that photo will temporarily have its focal length set to the number passed, and its aperture to f8") parser.add_option("--reset-settings", action="store_true", dest="reset", help=_("reset all program settings and preferences and exit")) (options, args) = parser.parse_args() @@ -3334,14 +3358,23 @@ def start(): sys.exit(0) if options.reset: - prefs = RapidPreferences() + prefs = prefsrapid.RapidPreferences() prefs.reset() print _("All settings and preferences have been reset") sys.exit(0) + + if options.focal_length: + focal_length = options.focal_length + else: + focal_length = None logger.info("Rapid Photo Downloader %s", utilities.human_readable_version(config.version)) logger.info("Using pyexiv2 %s", metadataphoto.pyexiv2_version_info()) logger.info("Using exiv2 %s", metadataphoto.exiv2_version_info()) + + if focal_length: + logger.info("Focal length of %s will be used when an aperture of 0.0 is encountered", focal_length) + if DOWNLOAD_VIDEO: logger.info("Using hachoir %s", metadatavideo.version_info()) else: @@ -3350,7 +3383,7 @@ def start(): bus = dbus.SessionBus () request = bus.request_name (config.DBUS_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE) if request != dbus.bus.REQUEST_NAME_REPLY_EXISTS: - app = RapidApp(bus, '/', config.DBUS_NAME) + app = RapidApp(bus, '/', config.DBUS_NAME, focal_length=focal_length) else: # this application is already running print "Rapid Photo Downloader is already running" diff --git a/rapid/scan.py b/rapid/scan.py index 5092f0a..0d7182c 100755 --- a/rapid/scan.py +++ b/rapid/scan.py @@ -19,6 +19,7 @@ import os import multiprocessing +import re import gio import gtk @@ -27,6 +28,7 @@ import pyexiv2 import rpdmultiprocessing as rpdmp import rpdfile +import prefsrapid import logging @@ -48,13 +50,20 @@ class Scan(multiprocessing.Process): files in bytes. """ - def __init__(self, path, batch_size, generate_folder, results_pipe, + def __init__(self, path, ignored_paths, use_re_ignored_paths, + batch_size, results_pipe, terminate_queue, run_event): """Setup values needed to conduct the scan. 'path' is a string of the path to be scanned, which is passed to gio. + 'ignored_paths' is a list of paths that should not be scanned. Any path + ending with one of the values will be ignored. + + 'use_re_ignored_paths': if true, pytho regular expressions will be used + to determine which paths to ignore + 'batch_size' is the number of files that should be sent back to the calling function at one time. @@ -69,16 +78,16 @@ class Scan(multiprocessing.Process): multiprocessing.Process.__init__(self) self.path = path + self.ignored_paths = ignored_paths + self.use_re_ignored_paths = use_re_ignored_paths self.results_pipe = results_pipe self.terminate_queue = terminate_queue self.run_event = run_event self.batch_size = batch_size - self.generate_folder = generate_folder self.counter = 0 self.files = [] self.file_type_counter = rpdfile.FileTypeCounter() - - + def _gio_scan(self, path, file_size_sum): """recursive function to scan a directory and its subdirectories for photos and possibly videos""" @@ -104,7 +113,8 @@ class Scan(multiprocessing.Process): file_type = child.get_file_type() name = child.get_name() if file_type == gio.FILE_TYPE_DIRECTORY: - file_size_sum = self._gio_scan(path.get_child(name), + if not self.ignore_this_path(name): + file_size_sum = self._gio_scan(path.get_child(name), file_size_sum) if file_size_sum is None: return None @@ -150,9 +160,16 @@ class Scan(multiprocessing.Process): def run(self): """start the actual scan.""" + + if self.use_re_ignored_paths and len(self.ignored_paths): + self.re_pattern = prefsrapid.check_and_compile_re(self.ignored_paths) + source = gio.File(self.path) try: - size = self._gio_scan(source, 0) + if not self.ignore_this_path(self.path): + size = self._gio_scan(source, 0) + else: + size = None except gio.Error, inst: logger.error("Error while scanning %s: %s", self.path, inst) size = None @@ -164,3 +181,21 @@ class Scan(multiprocessing.Process): self.results_pipe.send((rpdmp.CONN_COMPLETE, (size, self.file_type_counter, self.pid))) self.results_pipe.close() + + def ignore_this_path(self, path): + """ + determines if the path should be ignored according to the preferences + chosen by the user + """ + + if len(self.ignored_paths): + if self.use_re_ignored_paths and self.re_pattern: + # regular expressions are being used + if self.re_pattern.match(path): + return True + else: + # regular expressions are not being used + if path.endswith(tuple(self.ignored_paths)): + return True + + return False diff --git a/rapid/subfolderfile.py b/rapid/subfolderfile.py index 35bd74f..4b6cca1 100644 --- a/rapid/subfolderfile.py +++ b/rapid/subfolderfile.py @@ -136,7 +136,7 @@ def generate_name(rpd_file): class SubfolderFile(multiprocessing.Process): - def __init__(self, results_pipe, sequence_values): + def __init__(self, results_pipe, sequence_values, focal_length): multiprocessing.Process.__init__(self) self.daemon = True self.results_pipe = results_pipe @@ -150,6 +150,8 @@ class SubfolderFile(multiprocessing.Process): self.uses_session_sequece_no = sequence_values[6] self.uses_sequence_letter = sequence_values[7] + self.focal_length = focal_length + logger.debug("Start of day is set to %s", self.day_start.value) def progress_callback_no_update(self, amount_downloaded, total): @@ -308,8 +310,9 @@ class SubfolderFile(multiprocessing.Process): else: # Generate subfolder name and new file name generation_succeeded = True - experimental = False - if experimental and rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO: + + # check to see if focal length and aperture data should be manipulated + if self.focal_length is not None and rpd_file.file_type == rpdfile.FILE_TYPE_PHOTO: if load_metadata(rpd_file): a = rpd_file.metadata.aperture() if a == '0.0': |