diff -Nru gtkrawgallery-0.9.8/CHANGELOG.txt gtkrawgallery-0.9.9/CHANGELOG.txt --- gtkrawgallery-0.9.8/CHANGELOG.txt 2013-03-21 05:08:32.000000000 +0000 +++ gtkrawgallery-0.9.9/CHANGELOG.txt 2013-09-08 07:19:59.000000000 +0000 @@ -1,350 +1,365 @@ -GTKRawGallery changelog - -version 0.9.8 -------------- -+ DropBox uploader; -+ manual tagging; -+ Improved Exiftool interface. Implemented the batch mode to quickly retrieving metadata without the overhead of restarting the interpreter - at every call. -+ The curve tool was ported to Cairo avoiding random crashes due to gtk.curve issues. Now curve is stable, faster and portable. -+ Possibility to save dcraw presets; -+ New history tab on development window for best workflow control with possibility to save styles for batch conversion and apply styles - to photos; -+ New "Tags" tab on main window left panel for faster tag filtering and manual tagging; -+ Directory tree and album list have been switched to single click mode; -+ Advance Metadata Editor improvements; -+ Raw images are no long previewed on development window metadata tabs, but always extracted; -+ Development window fullscreen mode; -+ Drag and Drop from gallery to album list and tag list on left panel; -+ Smaller thumbnail size provided; -+ Gtk warnings no long logged; -+ Fix a bug on facebook login; -+ Fix a bug with non ascii names on sort_by_date function; - - -version 0.9.71 --------------- -+ Faster start time (about 20% of 0.9.7 version); -+ Fix "\fullsize_image.tiff': No such file or directory @ error/blob.c/OpenBlob/2589" on apply_effect method; -+ Fix broken pythonmagickwand package; - -version 0.9.7 --------------- -+ I am pleased to announce that Windows porting of GTKRawGallery was completed. - A Portable version will be released on the project page. -+ License was updated to GNU GPL v.3; -+ GUI appearance based on gtkrc style file and a new dark theme was provided as default; -+ Facebook, Flickr and Picasa Web Albums photo publisher; -+ Adds support for 3fr, ari, cap, iiq, eip, dcs, drf, mef, nrw, pxn, r3d, raw, rw1, rwz image formats; -+ Dcraw extensions are editable; -+ Possibility to manage photos on fullscreen mode; -+ Sorted tag list; -+ Multiple tag search; -+ Print dialog with embedded preview; -+ exif.py was updated to improve autorotation function; -+ Square thumbnails with rounded corners and minimized column spacing option; -+ Reverse function appliable to all gallery sort modes; -+ Color profile information now is retrieved by Exiftool; -+ Copy and move operations can be stopped; -+ New fullscreen gallery mode; -+ New development window split view; -+ Vignette removal tool; -+ Slideshow can be paused; -+ Tkinter no long required; -+ messages.pot was updated; -+ Fix men icon size; -+ Fix locale settings; -+ Fix a bug in zoom functions; -+ Fix some portability issues; -+ Fix broken barrel distortion removal for imagemagick version >= 6.7.0; - - -version 0.9.61 2011-02-23 --------------- -+ GTK UI was ported to the GTKBuilder format. Libglade no long required; -+ Message Dialogs were created at runtime; -+ liblcms was included in the package to permit the Profile Inspector to work on systems with lcms2 installed too; -+ Splash screen progressbar; -+ New "Reset Workflow" toolbutton; -+ New Print toolbutton on development window and ability to print a modified raw image directly; -+ Protection from undecodable image errors; -+ New "Sort by size" gallery option; -+ New square thumbnail option; -+ Fast thumbnail loading on Mail client and Burner; -+ Burn is disabled when no device found; -+ Fix some deadlocks on Curve tool (requires feedbacks); -+ Fix "Invalid Text Buffer Iterator" bug on ErrorLog class; -+ Fix random segmentation fault on gallery loading; -+ Fix not working autosave in save_workflow method; -+ Fix some portability issues; -+ Fix some code not thread-safe; - - - -version 0.9.6 2010-09-26 -------------- -+ Color Management based on ICC profile; -+ Basic ICC profile inspector; -+ Ports to Python 2.6 (2.5 no long supported!); -+ PIL is deprecated! Added a check for numpy support on pygtk in order to replace PIL on Magickwand to GDKPixbuf transformation - with increased performance. It also permits to eliminate the dependence from PIL; -+ Gettext support; -+ Provided messages.pot file for translators (read TRANSLATORS file for more information); -+ Adds support for .bay, .bmq, .cs1, .dc2, .fff, .k25, .mos, .rdc, .sr2 and .rw2 raw formats; -+ Checks for dcraw lcms support; -+ Updated Pythonmagickwand and removed max version block; -+ Possibility to send photos by mail; -+ Possibility to store photos on CD/DVD (requires cdrtools); -+ Possibility to rename multiple files with pattern; -+ Print support; -+ Exif, iptc and xmp metadata write support; -+ New Tag Manager design with advanced metadata editor and custom templates; -+ New "Clear keywords" button was added to the Tag Manager; -+ Tag Manager multilanguage support; -+ Tag Manager was switched to the main thread; -+ Metadata informations were separated by group and customizable "Favorites" tab was added; -+ Some shortcuts were changed; -+ Smaller thumbnail size option provided; -+ More image effects (charcoal, sketch, tint, posterize, spread, negate, radial blur, motion blur, adaptive blur); -+ Curves; -+ White balance; -+ Color temperature adjustment; -+ White balance preset editor! -+ Auto white balance presets (looks for presets stored on metadata); -+ Synchronize database tool can recognize keywords stored as list type by Exiftool; -+ Improved cache control; -+ New About dialog; -+ Faster Directory Tree. It was rewritten for best performance; -+ Error Log dialog allowing to read debugging information; -+ Removed Exif tab on settings dialog; -+ Removed Fontchooser on settings dialog to avoid display issues with font's unsupported characters; -+ GIO File Monitor for directory tree auto-updating; -+ Exiftool interface was rewritten using the more convenient JSON format; -+ Faster Synchronize database tool; -+ Batch manager improvements; -+ Batch queue , settings, albums and templates are stored as soon as modified to save them from improvvise crashes; -+ New "sort by date" gallery option; -+ Improved dependencies control; -+ Statistics were moved near histogram; -+ Added progressbar during raw extraction; -+ Added uninstall-gtkrawgallery launcher; -+ Added Disconnect togglebutton on dcraw GUI; -+ Added Rotation option on dcraw GUI; -+ "Default Colors" button on preferences now restores rc style colors; -+ Fast gallery refresh when reducing thumbnail size, on delete, move functions and layout switching; -+ Other GUI improvements; -+ Copy and Move functions were protected from insufficient disk space error; -+ Fix the issue of different behaviour with some image effects between scaled and full sized image; -+ Fix a bug on X3F and DNG files with embedded thumbnails as .ppm format; -+ Fix a functional error on synchronize database tool; -+ Fix a bug on slideshow background color; -+ Fix a bug on layout 3 image label updating; -+ Fix selection delay; -+ Fix desktop file errors; -+ Fix loss of keywords when album is renamed; -+ Fix save button's sensitivity setting on image effects; -+ Fix wrong image size on statusbar with some raw formats; - - -version 0.9.5 2009-07-18 -------------- -+ Updated to dcraw 8.94 new 16 bit with gamma option permitting to start the workflow from a gamma corrected 16 bit depth image (thank you Mr. Coffin!); -+ Setted 16 bit depth as default; -+ Red eye removal; -+ Lens distortion correction; -+ Oil Paint effect; -+ Speckle noise reduction; -+ Shadows/Highlights correction; -+ Updated loader.py (renamed gtkrg_loader.py) to load DrawingWand; -+ Updated setup.py to first remove old data files; -+ Updated Pythonmagickwand to Imagemagick 6.5.4 -+ Removed deprecated from Pythonmagickwand; -+ Added DrawingWand wrapper and other needed functions to Pythonmagickwand; -+ Fixed inverted size for rotated images on scale function; -+ Fixed "ImportError" for loader.py module due to the sys.prefix "bug"; -+ Fixed a bug on tag manager due to the missing selection control; -+ Fixed a bug in add_missed_tags function; - -version 0.9.4 2009-06-07 -------------- -+ Standard setup provided; -+ Uninstaller provided; -+ Command line access; -+ Sox dependence now is optional; -+ Added a GUI written with Tkinter to report missing dependencies; -+ Added library version control; -+ Added gamma correction to Dcraw GUI; -+ Added Median filter to Dcraw GUI (A slow function that can be an effective remedy against iso-noise); -+ New multi-threaded thumbnail generator for faster gallery loading; -+ Possibility to open files and folders dropped from window manager; -+ Desktop Entry specification compatibility permits to right click a file and open with/gtkrawgallery, - and associating raw images as all common image formats to be opened with GTKRawGallery; -+ New Sqlite3 dependence; -+ Album preview autorotation; -+ Fast tag search with database support; -+ Synchronize database tool (see README); -+ Metadata writing now is optional; -+ New "Write metadata" button on tag panel; -+ Metadata processing was switched to a separate thread - and progressbar window can be minimized; -+ "Clear database" button was added to the configuration dialog; -+ Image tags were added on thumbnail tooltip; -+ Keywords tag was added to exif list; -+ Sorted directory tree; -+ Hidden folders access; -+ Updated Pythonmagickwand to Imagemagick 6.5.3 -+ Thumbnail quality was improved; -+ Smoother crop selection (rewritten with cairo); -+ thumbnails loading was switched to a separate thread to unblock details window; -+ Opening folder or album during gallery loading was unblocked; -+ Image zooming was unabled also in not full size workflow mode; -+ Temporary directories were made into the system /tmp folder; -+ Settings database was stored into the /home/$user/.GTKRawGallery directory; -+ Fixed "trembling" bug on image dragging; -+ Fixed a bug on grep_image_size function; -+ Fixed a bug on rename function when overwrite dialog is called; -+ Fixed "NULL size bug" on scale function; -+ Fixed autorotation bug when "cache thumbnail" option was unchecked; -+ Added thumbnail modification control on thumbnail generator; -+ Delete function was rewritten to avoid gallery reloading on layout 3 - and quickly deleting wrong photos; -+ New "delete" button was added on details window; -+ The "remove from album" menuitems were removed and unified with "delete" function; -+ Added shortcuts; -+ Removed Fullscreen and Slideshow toolbuttons from details window; -+ Smoother slideshow's fading transition; -+ Details window shrinkage was unblocked for TwinView video mode; -+ "wand2pixbuf" function was improved; -+ Added exception handlers where needed; -+ Exiftool warning report; -+ Many code optimizations and fixes; - - -version 0.9.3 2008-10-21 -------------- -Updated PythonMagickWand to newer Imagemagick versions; - -version 0.9.2 2008-10-21 -------------- -Updated PythonMagickWand to newer Imagemagick versions; - -version 0.9.1 2008-10-18 -------------- -Fixed tollbutton sensitivity bug on main window; - -version 0.9 2008-10-15 ------------ -o New detailed and full featured Dcraw GUI with fine controls and automatic preview; -o Image histogram: a very important tool on digital photography; -o Color enhance tools; -o Image effects and transform tools; -o New Imagemagick dependence to permit a non destructive workflow; -o Splash screen; -o Batch Processor; -o Tagging; -o New over and under-exposed pixels buttons was added; -o Preserved metadata on "save_as_png" function; -o Useful "move", "copy", "rename" and "delete" functions was added to the browser; -o Detailed thumbnail tooltip; -o Thumbnail caching was optimized; -o Image handling with left mouse button; -o Automatic thumbnails scrolling on layouts 3 and 4; -o Zoom percentage on statusbar; -o Fixed "Open with Gimp" function bug; -o Fixed "dcraw options" function bug; -o Fixed "raw image dimensions" bug on statusbar and exif panel; -o Fixed "clear cache" bug; -o Autorotation code was optimized; - -version 0.6 2008-01-07 ------------ -o Deleted PIL dependence porting to gtk+ 2.10 -o Thumbnails cache costumizable; -o Added a new layout; -o Added thumbnail border costumizable; -o Added a fading transition in the slideshow; -o Background player in the slideshow; -o Album preview costumizable on left panel; -o Total size calculus; -o Cache level calculus; -o Automatic columns number calculus; -o Sorted folders on directory tree with autoexpansion at the last visited path; -o New Default colors button; -o New "Raw Preview" buttons on "Save As" dialog and Details window; -o New "Save as" and "Exit" buttons on Details window; -o New "Default Settings" button on Options dialog; -o New toolbar buttons; -o Fixed "Cannot rotate 48bit images" bug porting to gtk+ 2.10; -o Added input control in some dialogs; - -version 0.5.1 2007-10-31 -------------- -o Added Sorting function; -o Selection color costumizable; -o Fixed "Memory leak" bug; -o Fixed "Collapse Rows function" bug; - -version 0.5 2007-10-28 ------------- -o Changed gallery implementation to make raw thumbnails singly without preprocessing them all together, - that permits to enjoy immediately your photos! -o The "Open with" function was switched in a new thread for non-blocking work; -o Now you can browse raws inside directorys without write permission; -o Added a cache system for pixbuf loading improvement on detailed window and fullscreen mode; -o Preserved all metadatas recognized by Exiftool in the Conversion and "Save as" functions; -o Image Autoresizing on detailed window; -o New "Full Exif List" and "Hide Exif" buttons on detailed window; -o New "Open With" button on detailed window; -o Gallery, thumbnails label and treeviews colors costumizable; -o Costumizable thumbnails label font; -o New gallery view mode buttons; - -version 0.4 2007-10-04 ------------ -o Improved batch conversion code: thanks to Multi-Thread approach, - now you can browse your photos with non-blocking batch conversion in background; -o Rewrited exif parser with Exiftool only support for best compatibility with raw files and best performance; -o More complete exif data list; -o Added progressbar on "Save as" function; -o Customizable columns number of the gallery with new buttons on the main window; -o Added hotkeys on Full-screen mode view; -o Fixed "Beep doesn't sound on Xfce4" bug; -o Fixed "Cannot convert 48 bit tiff images" bug; -o Fixed "Exif IsoSpeed data not displayed on NEF raw files" -o Fixed some Dialogs' response bugs; - -version 0.3 2007-08-20 ------------ -o New "Save as" feature with exif data preserved; -o New "Convert" feature for batch conversion of multiple selection; -o New "Slideshow" button on main window; -o Code optimization; -o Graphic interface optimization; -o Fixed "Custom toolbar icons not showed" bug; -o Fixed other bugs - -version 0.2.2 2007-08-01 -------------- -o Fix some bugs; - -version 0.2.1 2007-07-31 -------------- -o Fix some bugs; - -version 0.2 2007-07-29 ------------ -o Completely rewritten functions for raw files management. - Now, you can quickly display your raw gallery and storage - your photo collection thanks to the new album manager; -o Thumbnail autorotation; -o New "Open with.." menuitem to open images with Gimp or other - editing tool; -o Best usability with "New Album" button on "Add Selection" dialog; -o Customizable album directory; -o Added "Remember last visited path" option; -o Several code optimizations; -o Fix known bugs; - -version 0.1 2007-06-27 ------------ -First version. +GTKRawGallery changelog + +version 0.9.9 +------------- ++ Included Dropbox SDK ++ New Webkit based facebook, Flickr and Dropbox authentication dialog; ++ Facebook, Flickr, Picasa Web Album and Dropbox uploaders with multiuser support; ++ Improved crop tool; ++ Updated messages.pot; ++ Italian translation; ++ Background music is stopped when slideshow is paused; ++ Added image size on thumbnail tooltips; ++ Fix broken dropbox uploader; ++ Fix facebook and Flickr authentication errors; ++ Fix display error on svg files; ++ Fix missing thumbnail border on gallery zoom out; + +version 0.9.8 +------------- ++ DropBox uploader; ++ manual tagging; ++ Improved Exiftool interface. Implemented the batch mode to quickly retrieving metadata without the overhead of restarting the interpreter + at every call. ++ The curve tool was ported to Cairo avoiding random crashes due to gtk.curve issues. Now curve is stable, faster and portable. ++ Possibility to save dcraw presets; ++ New history tab on development window for best workflow control with possibility to save styles for batch conversion and apply styles + to photos; ++ New "Tags" tab on main window left panel for faster tag filtering and manual tagging; ++ Directory tree and album list have been switched to single click mode; ++ Advance Metadata Editor improvements; ++ Raw images are no long previewed on development window metadata tabs, but always extracted; ++ Development window fullscreen mode; ++ Drag and Drop from gallery to album list and tag list on left panel; ++ Smaller thumbnail size provided; ++ Gtk warnings no long logged; ++ Fix a bug on facebook login; ++ Fix a bug with non ascii names on sort_by_date function; + + +version 0.9.71 +-------------- ++ Faster start time (about 20% of 0.9.7 version); ++ Fix "\fullsize_image.tiff': No such file or directory @ error/blob.c/OpenBlob/2589" on apply_effect method; ++ Fix broken pythonmagickwand package; + +version 0.9.7 +-------------- ++ I am pleased to announce that Windows porting of GTKRawGallery was completed. + A Portable version will be released on the project page. ++ License was updated to GNU GPL v.3; ++ GUI appearance based on gtkrc style file and a new dark theme was provided as default; ++ Facebook, Flickr and Picasa Web Albums photo publisher; ++ Adds support for 3fr, ari, cap, iiq, eip, dcs, drf, mef, nrw, pxn, r3d, raw, rw1, rwz image formats; ++ Dcraw extensions are editable; ++ Possibility to manage photos on fullscreen mode; ++ Sorted tag list; ++ Multiple tag search; ++ Print dialog with embedded preview; ++ exif.py was updated to improve autorotation function; ++ Square thumbnails with rounded corners and minimized column spacing option; ++ Reverse function appliable to all gallery sort modes; ++ Color profile information now is retrieved by Exiftool; ++ Copy and move operations can be stopped; ++ New fullscreen gallery mode; ++ New development window split view; ++ Vignette removal tool; ++ Slideshow can be paused; ++ Tkinter no long required; ++ messages.pot was updated; ++ Fix men icon size; ++ Fix locale settings; ++ Fix a bug in zoom functions; ++ Fix some portability issues; ++ Fix broken barrel distortion removal for imagemagick version >= 6.7.0; + + +version 0.9.61 2011-02-23 +-------------- ++ GTK UI was ported to the GTKBuilder format. Libglade no long required; ++ Message Dialogs were created at runtime; ++ liblcms was included in the package to permit the Profile Inspector to work on systems with lcms2 installed too; ++ Splash screen progressbar; ++ New "Reset Workflow" toolbutton; ++ New Print toolbutton on development window and ability to print a modified raw image directly; ++ Protection from undecodable image errors; ++ New "Sort by size" gallery option; ++ New square thumbnail option; ++ Fast thumbnail loading on Mail client and Burner; ++ Burn is disabled when no device found; ++ Fix some deadlocks on Curve tool (requires feedbacks); ++ Fix "Invalid Text Buffer Iterator" bug on ErrorLog class; ++ Fix random segmentation fault on gallery loading; ++ Fix not working autosave in save_workflow method; ++ Fix some portability issues; ++ Fix some code not thread-safe; + + + +version 0.9.6 2010-09-26 +------------- ++ Color Management based on ICC profile; ++ Basic ICC profile inspector; ++ Ports to Python 2.6 (2.5 no long supported!); ++ PIL is deprecated! Added a check for numpy support on pygtk in order to replace PIL on Magickwand to GDKPixbuf transformation + with increased performance. It also permits to eliminate the dependence from PIL; ++ Gettext support; ++ Provided messages.pot file for translators (read TRANSLATORS file for more information); ++ Adds support for .bay, .bmq, .cs1, .dc2, .fff, .k25, .mos, .rdc, .sr2 and .rw2 raw formats; ++ Checks for dcraw lcms support; ++ Updated Pythonmagickwand and removed max version block; ++ Possibility to send photos by mail; ++ Possibility to store photos on CD/DVD (requires cdrtools); ++ Possibility to rename multiple files with pattern; ++ Print support; ++ Exif, iptc and xmp metadata write support; ++ New Tag Manager design with advanced metadata editor and custom templates; ++ New "Clear keywords" button was added to the Tag Manager; ++ Tag Manager multilanguage support; ++ Tag Manager was switched to the main thread; ++ Metadata informations were separated by group and customizable "Favorites" tab was added; ++ Some shortcuts were changed; ++ Smaller thumbnail size option provided; ++ More image effects (charcoal, sketch, tint, posterize, spread, negate, radial blur, motion blur, adaptive blur); ++ Curves; ++ White balance; ++ Color temperature adjustment; ++ White balance preset editor! ++ Auto white balance presets (looks for presets stored on metadata); ++ Synchronize database tool can recognize keywords stored as list type by Exiftool; ++ Improved cache control; ++ New About dialog; ++ Faster Directory Tree. It was rewritten for best performance; ++ Error Log dialog allowing to read debugging information; ++ Removed Exif tab on settings dialog; ++ Removed Fontchooser on settings dialog to avoid display issues with font's unsupported characters; ++ GIO File Monitor for directory tree auto-updating; ++ Exiftool interface was rewritten using the more convenient JSON format; ++ Faster Synchronize database tool; ++ Batch manager improvements; ++ Batch queue , settings, albums and templates are stored as soon as modified to save them from improvvise crashes; ++ New "sort by date" gallery option; ++ Improved dependencies control; ++ Statistics were moved near histogram; ++ Added progressbar during raw extraction; ++ Added uninstall-gtkrawgallery launcher; ++ Added Disconnect togglebutton on dcraw GUI; ++ Added Rotation option on dcraw GUI; ++ "Default Colors" button on preferences now restores rc style colors; ++ Fast gallery refresh when reducing thumbnail size, on delete, move functions and layout switching; ++ Other GUI improvements; ++ Copy and Move functions were protected from insufficient disk space error; ++ Fix the issue of different behaviour with some image effects between scaled and full sized image; ++ Fix a bug on X3F and DNG files with embedded thumbnails as .ppm format; ++ Fix a functional error on synchronize database tool; ++ Fix a bug on slideshow background color; ++ Fix a bug on layout 3 image label updating; ++ Fix selection delay; ++ Fix desktop file errors; ++ Fix loss of keywords when album is renamed; ++ Fix save button's sensitivity setting on image effects; ++ Fix wrong image size on statusbar with some raw formats; + + +version 0.9.5 2009-07-18 +------------- ++ Updated to dcraw 8.94 new 16 bit with gamma option permitting to start the workflow from a gamma corrected 16 bit depth image (thank you Mr. Coffin!); ++ Setted 16 bit depth as default; ++ Red eye removal; ++ Lens distortion correction; ++ Oil Paint effect; ++ Speckle noise reduction; ++ Shadows/Highlights correction; ++ Updated loader.py (renamed gtkrg_loader.py) to load DrawingWand; ++ Updated setup.py to first remove old data files; ++ Updated Pythonmagickwand to Imagemagick 6.5.4 ++ Removed deprecated from Pythonmagickwand; ++ Added DrawingWand wrapper and other needed functions to Pythonmagickwand; ++ Fixed inverted size for rotated images on scale function; ++ Fixed "ImportError" for loader.py module due to the sys.prefix "bug"; ++ Fixed a bug on tag manager due to the missing selection control; ++ Fixed a bug in add_missed_tags function; + +version 0.9.4 2009-06-07 +------------- ++ Standard setup provided; ++ Uninstaller provided; ++ Command line access; ++ Sox dependence now is optional; ++ Added a GUI written with Tkinter to report missing dependencies; ++ Added library version control; ++ Added gamma correction to Dcraw GUI; ++ Added Median filter to Dcraw GUI (A slow function that can be an effective remedy against iso-noise); ++ New multi-threaded thumbnail generator for faster gallery loading; ++ Possibility to open files and folders dropped from window manager; ++ Desktop Entry specification compatibility permits to right click a file and open with/gtkrawgallery, + and associating raw images as all common image formats to be opened with GTKRawGallery; ++ New Sqlite3 dependence; ++ Album preview autorotation; ++ Fast tag search with database support; ++ Synchronize database tool (see README); ++ Metadata writing now is optional; ++ New "Write metadata" button on tag panel; ++ Metadata processing was switched to a separate thread + and progressbar window can be minimized; ++ "Clear database" button was added to the configuration dialog; ++ Image tags were added on thumbnail tooltip; ++ Keywords tag was added to exif list; ++ Sorted directory tree; ++ Hidden folders access; ++ Updated Pythonmagickwand to Imagemagick 6.5.3 ++ Thumbnail quality was improved; ++ Smoother crop selection (rewritten with cairo); ++ thumbnails loading was switched to a separate thread to unblock details window; ++ Opening folder or album during gallery loading was unblocked; ++ Image zooming was unabled also in not full size workflow mode; ++ Temporary directories were made into the system /tmp folder; ++ Settings database was stored into the /home/$user/.GTKRawGallery directory; ++ Fixed "trembling" bug on image dragging; ++ Fixed a bug on grep_image_size function; ++ Fixed a bug on rename function when overwrite dialog is called; ++ Fixed "NULL size bug" on scale function; ++ Fixed autorotation bug when "cache thumbnail" option was unchecked; ++ Added thumbnail modification control on thumbnail generator; ++ Delete function was rewritten to avoid gallery reloading on layout 3 + and quickly deleting wrong photos; ++ New "delete" button was added on details window; ++ The "remove from album" menuitems were removed and unified with "delete" function; ++ Added shortcuts; ++ Removed Fullscreen and Slideshow toolbuttons from details window; ++ Smoother slideshow's fading transition; ++ Details window shrinkage was unblocked for TwinView video mode; ++ "wand2pixbuf" function was improved; ++ Added exception handlers where needed; ++ Exiftool warning report; ++ Many code optimizations and fixes; + + +version 0.9.3 2008-10-21 +------------- +Updated PythonMagickWand to newer Imagemagick versions; + +version 0.9.2 2008-10-21 +------------- +Updated PythonMagickWand to newer Imagemagick versions; + +version 0.9.1 2008-10-18 +------------- +Fixed tollbutton sensitivity bug on main window; + +version 0.9 2008-10-15 +----------- +o New detailed and full featured Dcraw GUI with fine controls and automatic preview; +o Image histogram: a very important tool on digital photography; +o Color enhance tools; +o Image effects and transform tools; +o New Imagemagick dependence to permit a non destructive workflow; +o Splash screen; +o Batch Processor; +o Tagging; +o New over and under-exposed pixels buttons was added; +o Preserved metadata on "save_as_png" function; +o Useful "move", "copy", "rename" and "delete" functions was added to the browser; +o Detailed thumbnail tooltip; +o Thumbnail caching was optimized; +o Image handling with left mouse button; +o Automatic thumbnails scrolling on layouts 3 and 4; +o Zoom percentage on statusbar; +o Fixed "Open with Gimp" function bug; +o Fixed "dcraw options" function bug; +o Fixed "raw image dimensions" bug on statusbar and exif panel; +o Fixed "clear cache" bug; +o Autorotation code was optimized; + +version 0.6 2008-01-07 +----------- +o Deleted PIL dependence porting to gtk+ 2.10 +o Thumbnails cache costumizable; +o Added a new layout; +o Added thumbnail border costumizable; +o Added a fading transition in the slideshow; +o Background player in the slideshow; +o Album preview costumizable on left panel; +o Total size calculus; +o Cache level calculus; +o Automatic columns number calculus; +o Sorted folders on directory tree with autoexpansion at the last visited path; +o New Default colors button; +o New "Raw Preview" buttons on "Save As" dialog and Details window; +o New "Save as" and "Exit" buttons on Details window; +o New "Default Settings" button on Options dialog; +o New toolbar buttons; +o Fixed "Cannot rotate 48bit images" bug porting to gtk+ 2.10; +o Added input control in some dialogs; + +version 0.5.1 2007-10-31 +------------- +o Added Sorting function; +o Selection color costumizable; +o Fixed "Memory leak" bug; +o Fixed "Collapse Rows function" bug; + +version 0.5 2007-10-28 +------------ +o Changed gallery implementation to make raw thumbnails singly without preprocessing them all together, + that permits to enjoy immediately your photos! +o The "Open with" function was switched in a new thread for non-blocking work; +o Now you can browse raws inside directorys without write permission; +o Added a cache system for pixbuf loading improvement on detailed window and fullscreen mode; +o Preserved all metadatas recognized by Exiftool in the Conversion and "Save as" functions; +o Image Autoresizing on detailed window; +o New "Full Exif List" and "Hide Exif" buttons on detailed window; +o New "Open With" button on detailed window; +o Gallery, thumbnails label and treeviews colors costumizable; +o Costumizable thumbnails label font; +o New gallery view mode buttons; + +version 0.4 2007-10-04 +----------- +o Improved batch conversion code: thanks to Multi-Thread approach, + now you can browse your photos with non-blocking batch conversion in background; +o Rewrited exif parser with Exiftool only support for best compatibility with raw files and best performance; +o More complete exif data list; +o Added progressbar on "Save as" function; +o Customizable columns number of the gallery with new buttons on the main window; +o Added hotkeys on Full-screen mode view; +o Fixed "Beep doesn't sound on Xfce4" bug; +o Fixed "Cannot convert 48 bit tiff images" bug; +o Fixed "Exif IsoSpeed data not displayed on NEF raw files" +o Fixed some Dialogs' response bugs; + +version 0.3 2007-08-20 +----------- +o New "Save as" feature with exif data preserved; +o New "Convert" feature for batch conversion of multiple selection; +o New "Slideshow" button on main window; +o Code optimization; +o Graphic interface optimization; +o Fixed "Custom toolbar icons not showed" bug; +o Fixed other bugs + +version 0.2.2 2007-08-01 +------------- +o Fix some bugs; + +version 0.2.1 2007-07-31 +------------- +o Fix some bugs; + +version 0.2 2007-07-29 +----------- +o Completely rewritten functions for raw files management. + Now, you can quickly display your raw gallery and storage + your photo collection thanks to the new album manager; +o Thumbnail autorotation; +o New "Open with.." menuitem to open images with Gimp or other + editing tool; +o Best usability with "New Album" button on "Add Selection" dialog; +o Customizable album directory; +o Added "Remember last visited path" option; +o Several code optimizations; +o Fix known bugs; + +version 0.1 2007-06-27 +----------- +First version. diff -Nru gtkrawgallery-0.9.8/COPYING.txt gtkrawgallery-0.9.9/COPYING.txt --- gtkrawgallery-0.9.8/COPYING.txt 2013-03-21 05:08:32.000000000 +0000 +++ gtkrawgallery-0.9.9/COPYING.txt 2011-11-23 10:28:42.000000000 +0000 @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff -Nru gtkrawgallery-0.9.8/README.txt gtkrawgallery-0.9.9/README.txt --- gtkrawgallery-0.9.8/README.txt 2013-03-21 05:08:32.000000000 +0000 +++ gtkrawgallery-0.9.9/README.txt 2013-06-17 09:45:04.000000000 +0000 @@ -1,232 +1,236 @@ -GTKRawGallery: a workflow oriented photo retouching software and camera raw image developer. - -Version: 0.9.8 ------------------------------------------------------------------------------------------------------------ - -It is a workflow oriented photo retouching software with all necessary tools -to quickly process camera raw images. For this purpose GTKRawGallery brings -together the most powerfull open source software existing to work with 16 bit color depth images: -Dcraw, Imagemagick and Exiftool. - -Some important features: - + Image browser and album manager; - + Full featured Dcraw GUI for a fine-tuned raw image pre-processing; - + Image modification tools for 16 bit/channel post-processing; - + Batch conversion with styles; - + Batch Processor to speed up the workflow; - + Fast tagging; - + Advanced Metadata Editor with EXIF, IPTC and XMP write support; - + Color Management; - + Print support; - + Facebook, Flickr and Picasa Web Albums Publisher; - + DropBox Uploader; - - -Features --------- -Here is a list of the main features of GTKRawGallery: - + Non destructive image workflow with a color depth >=16 bit/channel; - + Most known image formats supported: jpg, tiff, png, xpm, xbm, gif, ico, cur, ppm , pbm, pgm, pnm, pcx, bmp, svg, ras, tga, targa, wmf, apm, wbmp; - + Camera raw files supported: cr2, nef, crw, raf, kdc, dcr, mrw, orf, ptx, pef, arw, srf, x3f, erf, dng, bay, bmq, cs1, dc2, fff, k25, mos, rdc, rw2. - For the updated list of supported cameras, visit Dcraw home page please; - + 16 bit/channel color depth images recognized; - + Advanced Metadata Editor with custom templates: a really powerfull and flexible tool adding write support to a huge list of metadata information; - + EXIF, IPTC, XMP and read and write support; - + Makernotes read support; - + Image tagging; - + Print support; - + Facebook, Flickr and Picasa Web Albums Publisher; - + DropBox Uploader; - + Color Manager based on ICC profiles (requires LittleCMS); - + Basic ICC profile inspector; - + Possibility to send photos by mail; - + Possibility to store photos on CD/DVD (requires cdrtools); - + Possibility to rename multiple files with pattern; - + Possibility to manage photos on fullscreen mode; - + Gallery fullscreen mode; - + Gettext support; - + full featured browser to copy, move, rename and delete images; - + Detail and thumbnail view for images; - + Thumbnails zooming; - + Thumbnails autorotation; - + Thumbnails cache; - + Full featured Dcraw GUI with fine controls, presets and automatic preview; - + White balance preset editor; - + Image histogram: a very important tool on digital photography; - + Color enhance tools (white balance, temperature, levels, curves, shadows, highlights, gamma, normalize, color balance, - exposure, saturation, hue, contrast, sigmoidal-contrast, channel mixer); - + Image transform tools (scale, rotate, flip, flop, fine rotation, border, crop, lens distortion, red eye removal, vignette removal); - + Image effects (sharpen, unsharp mask, blur, reduce noise, add noise, median filter, grayscale, sepia tone, despeckle, - oil paint, spread, tint, charcoal, sketch, posterize, negate); - + Full-screen view; - + Zooming and rotation; - + Slideshow with fading transition and background player (requires Sox); - + Album manager; - + Fast link to open images with Gimp or other editing tools; - + "Save as" and "Conversion" features with exif data preserved; - + Batch conversion with applicable style; - + Batch Processor; - + Workflow history manager; - + Direct metadata tagging for portability (see exiftool manual for supported file types) - and database support to quickly search by keywords; - + Synchronize Database tool to scan the filesystem for tagged images; - + Possibility to open files and folders dropped from the window manager; - + Command line access; - + Error Log dialog and error.log file to display and save debugging information; - + Four gallery layouts available; - + Dark theme or default Gtk theme available; - + User friendly graphic interface compatible with several screen resolutions (minimum 800x600) - thanks to many thumbnail settings and fullgallery view; - - -Hardware --------- -Ram >= 1Gb - - -Requirements ------------- -Python >= 2.6 -Gtk+ >= 2.12 -Pygtk >= 2.12 (requires pygobject, pycairo) -Imaging Python (PIL) >= 1.1.6 (Deprecated. Use Pygtk compiled with numpy support instead. See Tips and tricks section!) -Numpy >= 1.0.4 -Imagemagick-Q16 >= 6.4.8 -Exiftool >= 7.41 -Dcraw >= 8.93 - -Sox (optional) -Cdrtools (optional. Needs ASPI driver to anable cd burners on Windows XP) -LittleCMS (optional) - - -Imagemagick notes ------------------ -It's a powerfull library for image processing usually coming with most known Linux distributiones. -It is compiled with a default color depth of 16 bit (Q16), but it can be compiled as Q32 to preserve more color informations -during image processing or if you prefer, as HDRI (floating point quality) for a totally reversible and non destructive workflow. -See Imagemagick documentation for more information!. Default Q16 version is recommended! -This GTKRawGallery version was tested with imagemagick <= 6.7.5 to assure the wrapper compatibility. Newer versions might not work! -Just try! If "Undefined Simbol" or any error from Pythonmagickwand is raised, send a report to the project page's bug traker please! - - -Packages --------- -Google is your friend ;-) - -*For any trouble with packages, contact the mantainer please! - - -Installation from source ( Linux) ------------------------- -Unpack the tarball, then -$ cd /home/.../gtkrawgallery-version at your shell's prompt, -and finally as root: -# python setup.py install -or -$ sudo python setup.py install -for ubuntu users. - -Installation from source (Windows) ------------------------- -It is like a portable package, but without dependences so: -1) Download and install all requirements*; -2) Unpack the tarball; -3) Double click on gtkrawgallery.py to start; - -* Look for Gtk-2.16 runtime for performance issues with GDI+ based newer versions. - -Uninstall (Linux) ---------- -type as root at your shell's prompt: -# python /usr/[local]/bin/uninstall-gtkrawgallery -or -$ sudo python /usr/[local]/bin/uninstall-gtkrawgallery - - -Usage (Windows): ---------------- -Double click on gtkrawgallery.py to start; - -Usage (Linux): ------- -Depending of your system behavior type: -$ python /usr/local/bin/gtkrawgallery -or -$ python /usr/bin/gtkrawgallery - -or make a desktop launcher with the above command. - - -Tips and tricks: ---------------- -o Disable the thumbnails caching on single processor machines since it may slow down software usability; -o After every image modification, just right click on it to compare with unchanged image; -o Disable "Thumbnail autorotation" and "Thumbnail border" to speed up the gallery loading; -o Don't check the "Full image workflow" option if you don't need image zooming at high resolution, - but when you deal with raw images, to get max resolution, it is advisable to keep this option checked at cost of a longer processing time; -o Keep always checked the "Fast Loading" options on Dcraw settings page to speed up raw images' workflow (This option has not effect on saved images!); -o Batch conversion and Batch Processor do not overwrite; -o With the 0.9.4 version was added also the "Canc" keyboard shortcut, - that's very useful to quickly delete wrong photos; -o Always synchronize tags database if you want GTKRawGallery to recognize keywords tags written with other software. - That's very useful to migrate from other O.S. ;-) -o The "write metadata" button on details window's tag panel works overwriting metadata with checked tags, - so if nothing is checked, it will write a void string deleting all keywords!; -o The "Auto WB Presets" looks for presets stored on makernotes (only Canon, Pentax and what other cameras?) -o The 0.9.6 version introduced a check for Numpy support on Pygtk. It permits to replace PIL on Magickwand to GDKPixbuf - transformation and histogram computation with increased performance and a more responsive workflow. - When Pygtk is not compiled with Numpy support, a message dialog will alert you that PIL is required (if not installed)! - At this point, you can decide to install PIL or recompiling Pygtk (Numpy is handled by default); - -*When you process or save images, the workflow is applied to the full sized images! - - - -Bugs and Issues ---------------- -+ The transition could be very slow for images with alpha channel like png files (specially on old hardware and old X server). - Try reducing the number of frames or else disable it. - -+ GIO File Monitor support requires at least pygobject 2.15; - -+ Red eye removal may not work on imagemagick version < 6.7.0. In that case upgrade imagemagick. - -Other bugs are always probable. Open the Error Log dialog to see possible errors, also saved in the ~/.GTKRawGallery/error.log file. -Some errors like Pythonmagickwand and X11's could be handled only from a shell's prompt. -If you like the program and find anyone , it would be nice if you reported it! - - -License -------- -GNU GPL v.3 (see COPYING.txt) - - -To do ------ -Internationalization; - - - -Feedback --------- -Always very welcome :-) You can reach me at bit123@users.sourceforge.net - -Greatings ---------- -Thanks to Ian Stevens for permission to fork his Pythonmagickwand package; - - - - - - - - - - - - - -... to my wife. - +GTKRawGallery: a workflow oriented photo manager and camera raw image developer. + +Version: 0.9.9 +----------------------------------------------------------------------------------------------------------- + +It is a workflow oriented photo retouching software with all necessary tools +to quickly process camera raw images. For this purpose GTKRawGallery brings +together the most powerfull open source software existing to work with 16 bit color depth images: +Dcraw, Imagemagick and Exiftool. + +Some important features: + + Image browser and album manager; + + Full featured Dcraw GUI for a fine-tuned raw image pre-processing; + + Image modification tools for 16 bit/channel post-processing; + + Batch conversion with styles; + + Batch Processor to speed up the workflow; + + Fast tagging; + + Advanced Metadata Editor with EXIF, IPTC and XMP write support; + + Color Management; + + Print support; + + Facebook, Flickr and Picasa Web Albums Publisher; + + DropBox Uploader; + + +Features +-------- +Here is a list of the main features of GTKRawGallery: + + Non destructive image workflow with a color depth >=16 bit/channel; + + Most known image formats supported: jpg, tiff, png, xpm, xbm, gif, ico, cur, ppm , pbm, pgm, pnm, pcx, bmp, svg, ras, tga, targa, wmf, apm, wbmp; + + Camera raw files supported: cr2, nef, crw, raf, kdc, dcr, mrw, orf, ptx, pef, arw, srf, x3f, erf, dng, bay, bmq, cs1, dc2, fff, k25, mos, rdc, rw2. + For the updated list of supported cameras, visit Dcraw home page please; + + 16 bit/channel color depth images recognized; + + Advanced Metadata Editor with custom templates: a really powerfull and flexible tool adding write support to a huge list of metadata information; + + EXIF, IPTC, XMP and read and write support; + + Makernotes read support; + + Image tagging; + + Print support; + + Facebook, Flickr and Picasa Web Albums Publisher; + + DropBox Uploader; + + Color Manager based on ICC profiles (requires LittleCMS); + + Basic ICC profile inspector; + + Possibility to send photos by mail; + + Possibility to store photos on CD/DVD (requires cdrtools); + + Possibility to rename multiple files with pattern; + + Possibility to manage photos on fullscreen mode; + + Gallery fullscreen mode; + + Gettext support; + + full featured browser to copy, move, rename and delete images; + + Detail and thumbnail view for images; + + Thumbnails zooming; + + Thumbnails autorotation; + + Thumbnails cache; + + Full featured Dcraw GUI with fine controls, presets and automatic preview; + + White balance preset editor; + + Image histogram: a very important tool on digital photography; + + Color enhance tools (white balance, temperature, levels, curves, shadows, highlights, gamma, normalize, color balance, + exposure, saturation, hue, contrast, sigmoidal-contrast, channel mixer); + + Image transform tools (scale, rotate, flip, flop, fine rotation, border, crop, lens distortion, red eye removal, vignette removal); + + Image effects (sharpen, unsharp mask, blur, reduce noise, add noise, median filter, grayscale, sepia tone, despeckle, + oil paint, spread, tint, charcoal, sketch, posterize, negate); + + Full-screen view; + + Zooming and rotation; + + Slideshow with fading transition and background player (requires Sox); + + Album manager; + + Fast link to open images with Gimp or other editing tools; + + "Save as" and "Conversion" features with exif data preserved; + + Batch conversion with applicable style; + + Batch Processor; + + Workflow history manager; + + Direct metadata tagging for portability (see exiftool manual for supported file types) + and database support to quickly search by keywords; + + Synchronize Database tool to scan the filesystem for tagged images; + + Possibility to open files and folders dropped from the window manager; + + Command line access; + + Error Log dialog and error.log file to display and save debugging information; + + Four gallery layouts available; + + Dark theme or default Gtk theme available; + + User friendly graphic interface compatible with several screen resolutions (minimum 800x600) + thanks to many thumbnail settings and fullgallery view; + + +Hardware +-------- +Ram >= 1Gb + + +Requirements +------------ +Python >= 2.6 +Gtk+ >= 2.12 +Pygtk >= 2.12 (requires pygobject, pycairo) +Imaging Python (PIL) >= 1.1.6 +Numpy >= 1.0.4 +Imagemagick-Q16 >= 6.4.8 +Exiftool >= 7.41 +Dcraw >= 8.93 +PywebkitGTK+ (python-webkit) + +Sox (optional) +Cdrtools (optional. Needs ASPI driver to anable cd burners on Windows XP) +LittleCMS (optional) + + +Imagemagick notes +----------------- +It's a powerfull library for image processing usually coming with most known Linux distributiones. +It is compiled with a default color depth of 16 bit (Q16), but it can be compiled as Q32 to preserve more color informations +during image processing or if you prefer, as HDRI (floating point quality) for a totally reversible and non destructive workflow. +See Imagemagick documentation for more information!. Default Q16 version is recommended! +This GTKRawGallery version was tested with imagemagick <= 6.7.5 to assure the wrapper compatibility. Newer versions might not work! +Just try! If "Undefined Simbol" or any error from Pythonmagickwand is raised, send a report to the project page's bug traker please! + + +Packages +-------- +A Slackware package can be found at the www.slacky.eu repository; +An Debian package can be found at the www.getdeb.net repository; +Anyway Google is your friend ;-) + +*For any trouble with packages, contact the mantainer please! + + +Installation from source ( Linux) +------------------------ +Unpack the tarball, then +$ cd /home/.../gtkrawgallery-version at your shell's prompt, +and finally as root: +# python setup.py install +or +$ sudo python setup.py install +for ubuntu users. + +Installation from source (Windows) +------------------------ +It is like a portable package, but without dependences so: +1) Download and install all requirements*; +2) Unpack the tarball; +3) Double click on gtkrawgallery.py to start; + +* Look for Gtk-2.16 runtime for performance issues with GDI+ based newer versions. + +Uninstall (Linux) +--------- +type as root at your shell's prompt: +# python /usr/[local]/bin/uninstall-gtkrawgallery +or +$ sudo python /usr/[local]/bin/uninstall-gtkrawgallery + + +Usage (Windows): +--------------- +Double click on gtkrawgallery.py to start; + +Usage (Linux): +------ +Depending of your system behavior type: +$ python /usr/local/bin/gtkrawgallery +or +$ python /usr/bin/gtkrawgallery + +or make a desktop launcher with the above command. + + +Tips and tricks: +--------------- +o Disable the thumbnails caching on single processor machines since it may slow down software usability; +o After every image modification, just right click on it to compare with unchanged image; +o Disable "Thumbnail autorotation" and "Thumbnail border" to speed up the gallery loading; +o Don't check the "Full image workflow" option if you don't need image zooming at high resolution, + but when you deal with raw images, to get max resolution, it is advisable to keep this option checked at cost of a longer processing time; +o Keep always checked the "Fast Loading" options on Dcraw settings page to speed up raw images' workflow (This option has not effect on saved images!); +o Batch conversion and Batch Processor do not overwrite; +o With the 0.9.4 version was added also the "Canc" keyboard shortcut, + that's very useful to quickly delete wrong photos; +o Always synchronize tags database if you want GTKRawGallery to recognize keywords tags written with other software. + That's very useful to migrate from other O.S. ;-) +o The "write metadata" button on details window's tag panel works overwriting metadata with checked tags, + so if nothing is checked, it will write a void string deleting all keywords!; +o The "Auto WB Presets" looks for presets stored on makernotes (only Canon, Pentax and what other cameras?) +o The 0.9.6 version introduced a check for Numpy support on Pygtk. It permits to replace PIL on Magickwand to GDKPixbuf + transformation and histogram computation with increased performance and a more responsive workflow. + When Pygtk is not compiled with Numpy support, a message dialog will alert you that PIL is required (if not installed)! + At this point, you can decide to install PIL or recompiling Pygtk (Numpy is handled by default); + +*When you process or save images, the workflow is applied to the full sized images! + + + +Bugs and Issues +--------------- ++ The transition could be very slow for images with alpha channel like png files (specially on old hardware and old X server). + Try reducing the number of frames or else disable it. + ++ GIO File Monitor support requires at least pygobject 2.15; + ++ Red eye removal may not work on imagemagick version < 6.7.0. In that case upgrade imagemagick. + +Other bugs are always probable. Open the Error Log dialog to see possible errors, also saved in the ~/.GTKRawGallery/error.log file. +Some errors like Pythonmagickwand and X11's could be handled only from a shell's prompt. +If you like the program and find anyone , it would be nice if you reported it! + + +License +------- +GNU GPL v.3 (see COPYING.txt) + + +To do +----- +Internationalization; +Porting to GTK+ 3 with gobject-introspection; + + + +Feedback +-------- +Always very welcome :-) Make a post on the project page forum at http://sourceforge.net/p/gtkrawgallery/discussion/ , please! + +Greatings +--------- +Thanks to Ian Stevens for permission to fork his Pythonmagickwand package; + + + + + + + + + + + + + +... to my wife. + diff -Nru gtkrawgallery-0.9.8/TRANSLATORS.txt gtkrawgallery-0.9.9/TRANSLATORS.txt --- gtkrawgallery-0.9.8/TRANSLATORS.txt 2013-03-21 05:08:32.000000000 +0000 +++ gtkrawgallery-0.9.9/TRANSLATORS.txt 2010-07-10 10:11:36.000000000 +0000 @@ -1,21 +1,21 @@ -If you find this software useful and are capable of -adding a language that has not yet been translated, it would be -greatly appreciated if you sended the translated .po file -to -so that your name will be included between official translators. - -Manual procedure ----------------- -1) Install gettext tools; -2) Make a .po file for desired locale language. Here is an example for french: - >msginit -i messages.pot -o fr.po --locale=fr_FR -3) Edit fr.po with translated strings (if you prefer a GUI try Poedit or KBabel); -4) Compile fr.po with msgfmt and save the output file as gtkrawgallery.mo in the right locale_dir*: - >msgfmt -o locale_dir/fr/LC_MESSAGES/gtkrawgallery.mo fr.po - -*locale_dir is usualy "/usr//share/locale" on Linux. - -Automatic procedure -------------------- -Poedit (www.Poedit.org) permits to make the .po file from a provided messages.pot -and saves it with .mo extension after the translation. +If you find this software useful and are capable of +adding a language that has not yet been translated, it would be +greatly appreciated if you sended the translated .po file +to +so that your name will be included between official translators. + +Manual procedure +---------------- +1) Install gettext tools; +2) Make a .po file for desired locale language. Here is an example for french: + >msginit -i messages.pot -o fr.po --locale=fr_FR +3) Edit fr.po with translated strings (if you prefer a GUI try Poedit or KBabel); +4) Compile fr.po with msgfmt and save the output file as gtkrawgallery.mo in the right locale_dir*: + >msgfmt -o locale_dir/fr/LC_MESSAGES/gtkrawgallery.mo fr.po + +*locale_dir is usualy "/usr//share/locale" on Linux. + +Automatic procedure +------------------- +Poedit (www.Poedit.org) permits to make the .po file from a provided messages.pot +and saves it with .mo extension after the translation. diff -Nru gtkrawgallery-0.9.8/debian/changelog gtkrawgallery-0.9.9/debian/changelog --- gtkrawgallery-0.9.8/debian/changelog 2013-03-22 07:01:58.000000000 +0000 +++ gtkrawgallery-0.9.9/debian/changelog 2013-09-24 08:57:52.000000000 +0000 @@ -1,3 +1,20 @@ +gtkrawgallery (0.9.9-1dhor~all) all; urgency=low + + * Included Dropbox SDK + * New Webkit based facebook, Flickr and Dropbox authentication dialog; + * Facebook, Flickr, Picasa Web Album and Dropbox uploaders with multiuser support; + * Improved crop tool; + * Updated messages.pot; + * Italian translation; + * Background music is stopped when slideshow is paused; + * Added image size on thumbnail tooltips; + * Fix broken dropbox uploader; + * Fix facebook and Flickr authentication errors; + * Fix display error on svg files; + * Fix missing thumbnail border on gallery zoom out; + + -- Dariusz Duma Tue, 24 Sep 2013 10:50:46 +0200 + gtkrawgallery (0.9.8-2dhor~all) all; urgency=low * DropBox uploader diff -Nru gtkrawgallery-0.9.8/debian/control gtkrawgallery-0.9.9/debian/control --- gtkrawgallery-0.9.8/debian/control 2013-03-22 07:01:49.000000000 +0000 +++ gtkrawgallery-0.9.9/debian/control 2013-09-27 13:37:40.000000000 +0000 @@ -11,6 +11,7 @@ Package: gtkrawgallery Architecture: any +Conflicts: python-gdata Depends: ${shlibs:Depends},${python:Depends}, liblcms1, python | python2.7, python-numpy, dcraw (>= 8.93), libimage-exiftool-perl, imagemagick, python-cairo, python-gobject, python-gtk2, wodim Description: GTKRawGallery manages and edits RAW files GTKRawGallery is a workflow oriented photo retouching software for diff -Nru gtkrawgallery-0.9.8/debian/patches/gtkrawgallery gtkrawgallery-0.9.9/debian/patches/gtkrawgallery --- gtkrawgallery-0.9.8/debian/patches/gtkrawgallery 2013-03-22 07:36:14.000000000 +0000 +++ gtkrawgallery-0.9.9/debian/patches/gtkrawgallery 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -Description: - TODO: Put a short summary on the line above and replace this paragraph - with a longer explanation of this change. Complete the meta-information - with other relevant fields (see below for details). To make it easier, the - information below has been extracted from the changelog. Adjust it or drop - it. - . - gtkrawgallery (0.9.8-2dhor~all) all; urgency=low - . - * DropBox uploader - * manual tagging - * Improved Exiftool interface. Implemented the batch mode to quickly retrieving metadata without the overhead of restarting the interpreter at every call. - * The curve tool was ported to Cairo avoiding random crashes due to gtk.curve issues. Now curve is stable, faster and portable. - * Possibility to save dcraw presets - * New history tab on development window for best workflow control with possibility to save styles for batch conversion and apply styles to photos - * New "Tags" tab on main window left panel for faster tag filtering and manual tagging - * Directory tree and album list have been switched to single click mode - * Advance Metadata Editor improvements - * Raw images are no long previewed on development window metadata tabs, but always extracted - * Development window fullscreen mode - * Drag and Drop from gallery to album list and tag list on left panel - * Smaller thumbnail size provided - * Gtk warnings no long logged - * Fix a bug on facebook login - * Fix a bug with non ascii names on sort_by_date function -Author: Dariusz Duma - ---- -The information above should follow the Patch Tagging Guidelines, please -checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here -are templates for supplementary fields that you might want to add: - -Origin: , -Bug: -Bug-Debian: http://bugs.debian.org/ -Bug-Ubuntu: https://launchpad.net/bugs/ -Forwarded: -Reviewed-By: -Last-Update: - ---- gtkrawgallery-0.9.8.orig/gtkrawgallery -+++ gtkrawgallery-0.9.8/gtkrawgallery -@@ -1,21 +1,21 @@ --#!/usr/bin/env python --''' --gtkrawgallery: GTKRawGallery launcher for Linux; --To start GTKRawGallery type from a shell prompt: --$ python /usr/bin/gtkrawgallery --or --$ python /usr/local/bin/gtkrawgallery --depending of your system prefix. --comment: A workflow oriented photo manager for digital camera raw image development; --author: (c) 2011 Daniele Isca --license: GNU GPL v.3 (see COPYING file) --version: 0.9.7 --This software comes with no warranty of any kind, use at your own risk! --''' -- --import gtkrawgallery -- --if __name__ == "__main__": -- gtkrg = gtkrawgallery.Gallery() -- gtkrg.main() -- -+#!/usr/bin/env python -+''' -+gtkrawgallery: GTKRawGallery launcher for Linux; -+To start GTKRawGallery type from a shell prompt: -+$ python /usr/bin/gtkrawgallery -+or -+$ python /usr/local/bin/gtkrawgallery -+depending of your system prefix. -+comment: A workflow oriented photo manager for digital camera raw image development; -+author: (c) 2011 Daniele Isca -+license: GNU GPL v.3 (see COPYING file) -+version: 0.9.7 -+This software comes with no warranty of any kind, use at your own risk! -+''' -+ -+import gtkrawgallery -+ -+if __name__ == "__main__": -+ gtkrg = gtkrawgallery.Gallery() -+ gtkrg.main() -+ diff -Nru gtkrawgallery-0.9.8/debian/patches/series gtkrawgallery-0.9.9/debian/patches/series --- gtkrawgallery-0.9.8/debian/patches/series 2013-03-22 07:36:04.000000000 +0000 +++ gtkrawgallery-0.9.9/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -gtkrawgallery diff -Nru gtkrawgallery-0.9.8/gtkrawgallery gtkrawgallery-0.9.9/gtkrawgallery --- gtkrawgallery-0.9.8/gtkrawgallery 2012-01-21 10:20:22.000000000 +0000 +++ gtkrawgallery-0.9.9/gtkrawgallery 2013-09-24 10:42:46.000000000 +0000 @@ -1,21 +1,21 @@ -#!/usr/bin/env python -''' -gtkrawgallery: GTKRawGallery launcher for Linux; -To start GTKRawGallery type from a shell prompt: -$ python /usr/bin/gtkrawgallery -or -$ python /usr/local/bin/gtkrawgallery -depending of your system prefix. -comment: A workflow oriented photo manager for digital camera raw image development; -author: (c) 2011 Daniele Isca -license: GNU GPL v.3 (see COPYING file) -version: 0.9.7 -This software comes with no warranty of any kind, use at your own risk! -''' - -import gtkrawgallery - -if __name__ == "__main__": - gtkrg = gtkrawgallery.Gallery() - gtkrg.main() - +#!/usr/bin/env python +''' +gtkrawgallery: GTKRawGallery launcher for Linux; +To start GTKRawGallery type from a shell prompt: +$ python /usr/bin/gtkrawgallery +or +$ python /usr/local/bin/gtkrawgallery +depending of your system prefix. +comment: A workflow oriented photo manager for digital camera raw image development; +author: (c) 2013 Daniele Isca +license: GNU GPL v.3 (see COPYING file) +version: 0.9.9 +This software comes with no warranty of any kind, use at your own risk! +''' + +import gtkrawgallery + +if __name__ == "__main__": + gtkrg = gtkrawgallery.Gallery() + gtkrg.main() + diff -Nru gtkrawgallery-0.9.8/gtkrawgallery.py gtkrawgallery-0.9.9/gtkrawgallery.py --- gtkrawgallery-0.9.8/gtkrawgallery.py 2013-03-21 04:03:33.000000000 +0000 +++ gtkrawgallery-0.9.9/gtkrawgallery.py 2013-09-24 08:49:53.000000000 +0000 @@ -1,5 +1,5 @@ #!/usr/bin/env python -# -*- coding: cp1252 -*- +# -*- coding: utf-8 -*- ''' gtkrawgallery.py: GTKRawGallery main script; comment: A workflow oriented photo manager for digital camera raw image development; @@ -7,8 +7,8 @@ license: GNU GPL v.3 (see file COPYING.txt); This software comes with no warranty of any kind, use at your own risk! ''' -__VERSION__ = u'0.9.8' -__COPYRIGHT__ = u'Copyright 2013 Daniele Isca' +__VERSION__ = u'0.9.9' +__COPYRIGHT__ = u'Copyright © 2013 Daniele Isca' RAW_FORMATS = ('.cr2', '.nef', '.crw', '.raf', '.kdc', '.dcr', '.mrw', '.sr2', '.orf', '.ptx', '.pef', '.arw', '.srf', '.x3f', '.erf', '.dng', @@ -21,15 +21,16 @@ import sys import time import cPickle +import warnings homepath = '/' dcraw = 'dcraw' exiftool = 'exiftool' CREATIONFLAGS = 0 -if os.path.isdir('/usr/share/locale'): +if os.path.isfile('/usr/share/locale/it/LC_MESSAGES/gtkrawgallery.mo'): locale_path = '/usr/share/locale' -elif os.path.isdir('/usr/local/share/locale'): +else: locale_path = '/usr/local/share/locale' if os.path.isdir('/usr/local/share/gtkrawgallery'): @@ -39,16 +40,16 @@ elif sys.platform.startswith('win'): gtkrg_datadir = os.path.join(os.getcwd(), 'src') homepath = '\\' - locale_path = 'locale' CREATIONFLAGS = 134217728 dcraw = os.path.join('bin','dcraw') exiftool = os.path.join('bin','exiftool') sys.path.append(os.path.join(os.getcwd(), 'bin')) - os.environ['PATH'] += ';bin;lib;' + os.environ['PATH'] += ';bin' + os.environ['PATH'] += ';C:\Python27\Lib\site-packages\gtk-2.16.6' os.environ['PATH'] += ';src\ImageMagick' os.environ['MAGICK_CONFIGURE_PATH'] = 'src\ImageMagick\config' os.environ['MAGICK_CODER_MODULE_PATH'] = 'src\ImageMagick\modules\coders' - + locale_path = os.path.join(os.getcwd(), 'locale') else: print "/usr/local/share/gtkrawgallery; /usr/share/gtkrawgallery: directory not found!" sys.exit(1) @@ -66,12 +67,14 @@ loader.check_python_version() gtk = loader.load_module('gtk') pygtk = loader.load_module('pygtk') +pygtk.require('2.0') stgs = gtk.settings_get_default() stgs.set_long_property('gtk-menu-images', 1, '') stgs.set_long_property('gtk-button-images', 1, '') GTKRG_SETTINGS = {} +warnings.simplefilter('ignore', gtk.Warning) if os.path.isfile(os.path.join(os.path.expanduser('~/.GTKRawGallery'), 'settings')): - f = open(os.path.join(os.path.expanduser('~/.GTKRawGallery'), 'settings')) + f = open(os.path.join(os.path.expanduser('~/.GTKRawGallery'), 'settings'), 'rb') GTKRG_SETTINGS = cPickle.load(f) f.close() if GTKRG_SETTINGS.get('dark_theme') in (True, None) and os.path.exists(os.path.join(gtkrg_datadir, 'gtkrc')): @@ -104,7 +107,6 @@ numpy = loader.load_module('numpy') pygtk_has_numpy_support = loader.check_pygtk_numpy_support() if not pygtk_has_numpy_support: - # print 'DeprecationWarning: Pygtk is not compiled with Numpy support.\nConsider to recompile Pygtk with Numpy support for best performance.\nTrying to load PIL...' pulse_splash(splash_pb, 0.25, 'Loading Imaging Python...') Image = loader.load_module('Image') print 'PIL loaded' @@ -121,6 +123,7 @@ pulse_splash(splash_pb, 0.5, 'Looking for Lcms...') DELEGATES = loader.get_delegates() pulse_splash(splash_pb, 0.55, 'Loading builtin modules...') + from subprocess import * from tempfile import mkdtemp from string import ascii_uppercase @@ -130,7 +133,6 @@ import gc import shutil import traceback -import warnings import gettext import json import exif @@ -143,12 +145,9 @@ encoding = locale.getpreferredencoding() utf8conv = lambda x : unicode(x, encoding, errors='replace').encode('utf8') unistr = lambda x : unicode(x, encoding, errors='replace') - -locale.setlocale(locale.LC_ALL, '') -gettext.bindtextdomain('gtkrawgallery', locale_path) -gettext.textdomain('gtkrawgallery') -_ = gettext.gettext -gettext.install('gtkrawgallery', locale_path, unicode=1) + +import intl +intl.install('gtkrawgallery', locale_path) class ErrorLog: @@ -177,10 +176,10 @@ def __init__(self): self.unistr = unistr self.wtree = gtk.Builder() + self.wtree.set_translation_domain('gtkrawgallery') self.wtree.add_from_file(os.path.join(gtkrg_datadir, 'gtkrg.ui')) self.load_widgets() self.setup_signal_handlers() - self._id = None self.CREATIONFLAGS = CREATIONFLAGS self.i = 0 self.radius = 20 @@ -266,7 +265,7 @@ self.overwrite = False self.stop_metadata_toggle = False self.history = [] - self.workflow_history = ['original'] + self.workflow_history = [_('Original')] self.workflow_copy = [] self.level_hist = [] self.bright_sat = [0,0,0] @@ -352,11 +351,13 @@ self.boxmodel21 = gtk.ListStore(str) self.boxmodel22 = gtk.ListStore(str) self.boxmodel24 = gtk.ListStore(str) + self.boxmodel27 = gtk.ListStore(str) self.boxmodel30 = gtk.ListStore(str) self.boxmodel31 = gtk.ListStore(str) self.boxmodel33 = gtk.ListStore(str) self.boxmodel34 = gtk.ListStore(str) self.boxmodel35 = gtk.ListStore(str) + self.boxmodel38 = gtk.ListStore(str) self.boxmodel40 = gtk.ListStore(str) self.boxmodel41 = gtk.ListStore(str) self.boxmodel42 = gtk.ListStore(str) @@ -369,7 +370,7 @@ cell = gtk.CellRendererText() pb_renderer = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn() - column.set_title('Albums') + column.set_title(_('Albums')) column.pack_start(pb_renderer, expand=False) column.add_attribute(pb_renderer, 'pixbuf', 0) column.pack_start(cell, expand=True) @@ -383,7 +384,7 @@ #============= self.treeview2 struct ========================================================= self.treestore = gtk.TreeStore(gtk.gdk.Pixbuf, str) - column0 = gtk.TreeViewColumn("Directory Tree") + column0 = gtk.TreeViewColumn(_("Directory Tree")) self.treeview2.append_column(column0) cellpb = gtk.CellRendererPixbuf() cell = gtk.CellRendererText() @@ -462,7 +463,7 @@ column_tag_toggle.set_attributes(tag_toggle, active=0) self.treeview5.append_column(column_tag_toggle) renderer_tag_name = gtk.CellRendererText() - column_tag_name = gtk.TreeViewColumn("Keywords", renderer_tag_name, text=1) + column_tag_name = gtk.TreeViewColumn(_("Keywords"), renderer_tag_name, text=1) self.treeview5.append_column(column_tag_name) self.treeview5.set_model(self.tagmodel) @@ -476,7 +477,7 @@ column_tag_toggle2.set_attributes(self.tag_toggle2, active=0) self.treeview6.append_column(column_tag_toggle2) renderer_tag_name2 = gtk.CellRendererText() - column_tag_name2 = gtk.TreeViewColumn("Keywords", renderer_tag_name2, text=1) + column_tag_name2 = gtk.TreeViewColumn(_("Keywords"), renderer_tag_name2, text=1) self.treeview6.append_column(column_tag_name2) self.treeview6.set_model(self.tagmodel2) @@ -520,6 +521,14 @@ self.combobox24.set_model(self.boxmodel24) self.combobox24.pack_start(cell, True) self.combobox24.add_attribute(cell, 'text', 0) + + self.combobox27.set_model(self.boxmodel27) + self.combobox27.pack_start(cell, True) + self.combobox27.add_attribute(cell, 'text', 0) + self.boxmodel27.append([_('White')]) + self.boxmodel27.append([_('Grey')]) + self.boxmodel27.append([_('Black')]) + self.combobox28.set_model(self.boxmodel) self.combobox28.pack_start(cell, True) self.combobox28.add_attribute(cell, 'text', 0) @@ -557,8 +566,8 @@ self.combobox40.set_model(self.boxmodel40) self.combobox40.pack_start(cell, True) self.combobox40.add_attribute(cell, 'text', 0) - self.boxmodel40.append(['Intersection']) - self.boxmodel40.append(['Union']) + self.boxmodel40.append([_('Intersection')]) + self.boxmodel40.append([_('Union')]) self.combobox41.set_model(self.boxmodel41) self.combobox41.pack_start(cell, True) self.combobox41.add_attribute(cell, 'text', 0) @@ -577,6 +586,13 @@ self.combobox17.set_model(self.boxmodel17) self.combobox17.pack_start(cell, True) self.combobox17.add_attribute(cell, 'text', 0) + + self.combobox38.set_model(self.boxmodel38) + self.combobox38.pack_start(cell, True) + self.combobox38.add_attribute(cell, 'text', 0) + self.boxmodel38.append([_('Keep embedded profile as input profile')]) + self.boxmodel38.append([_('Ignore embedded profile')]) + self.boxmodel38.append([_('Ask always')]) #=============== self.treeview7 struct ======================================================== @@ -585,7 +601,7 @@ tv7_column1 = gtk.TreeViewColumn('Filename', tv7_cell1, text=0) self.treeview7.append_column(tv7_column1) tv7_cell2 = gtk.CellRendererText() - tv7_column2 = gtk.TreeViewColumn('Tags', tv7_cell2, text=1) + tv7_column2 = gtk.TreeViewColumn(_('Tags'), tv7_cell2, text=1) self.treeview7.append_column(tv7_column2) self.treeview7.set_model(self.tv7_model) @@ -612,10 +628,10 @@ self.attachment_model = gtk.ListStore(gtk.gdk.Pixbuf, str) renderer_pix = gtk.CellRendererPixbuf() - column_pix = gtk.TreeViewColumn("Preview", renderer_pix, pixbuf=0) + column_pix = gtk.TreeViewColumn(_("Preview"), renderer_pix, pixbuf=0) self.treeview9.append_column(column_pix) cell = gtk.CellRendererText() - column_item = gtk.TreeViewColumn("Attachment", cell, text=1) + column_item = gtk.TreeViewColumn(_("Attachment"), cell, text=1) column_item.set_resizable(True) self.treeview9.append_column(column_item) self.treeview9.set_model(self.attachment_model) @@ -626,7 +642,7 @@ tag_toggle = gtk.CellRendererToggle() tag_toggle.set_property('activatable', True) tag_toggle.connect('toggled', self.on_metadata_tab_toggled, self.exif_tab_model) - column_tag_toggle = gtk.TreeViewColumn("Favorites", tag_toggle) + column_tag_toggle = gtk.TreeViewColumn(_("Favorites"), tag_toggle) column_tag_toggle.set_attributes(tag_toggle, active=0) self.treeview10.append_column(column_tag_toggle) renderer_tag_name = gtk.CellRendererText() @@ -643,7 +659,7 @@ tag_toggle = gtk.CellRendererToggle() tag_toggle.set_property('activatable', True) tag_toggle.connect('toggled', self.on_metadata_tab_toggled, self.iptc_tab_model) - column_tag_toggle = gtk.TreeViewColumn("Favorites", tag_toggle) + column_tag_toggle = gtk.TreeViewColumn(_("Favorites"), tag_toggle) column_tag_toggle.set_attributes(tag_toggle, active=0) self.treeview11.append_column(column_tag_toggle) renderer_tag_name = gtk.CellRendererText() @@ -660,7 +676,7 @@ tag_toggle = gtk.CellRendererToggle() tag_toggle.set_property('activatable', True) tag_toggle.connect('toggled', self.on_metadata_tab_toggled, self.xmp_tab_model) - column_tag_toggle = gtk.TreeViewColumn("Favorites", tag_toggle) + column_tag_toggle = gtk.TreeViewColumn(_("Favorites"), tag_toggle) column_tag_toggle.set_attributes(tag_toggle, active=0) self.treeview12.append_column(column_tag_toggle) renderer_tag_name = gtk.CellRendererText() @@ -677,7 +693,7 @@ tag_toggle = gtk.CellRendererToggle() tag_toggle.set_property('activatable', True) tag_toggle.connect('toggled', self.on_metadata_tab_toggled, self.makernotes_tab_model) - column_tag_toggle = gtk.TreeViewColumn("Favorites", tag_toggle) + column_tag_toggle = gtk.TreeViewColumn(_("Favorites"), tag_toggle) column_tag_toggle.set_attributes(tag_toggle, active=0) self.treeview13.append_column(column_tag_toggle) renderer_tag_name = gtk.CellRendererText() @@ -713,11 +729,11 @@ toggle = gtk.CellRendererToggle() toggle.set_property('activatable', True) toggle.connect('toggled', self.on_cdmodel_toggled, self.cdmodel) - column_toggle = gtk.TreeViewColumn("Enabled", toggle) + column_toggle = gtk.TreeViewColumn(_("Enabled"), toggle) column_toggle.set_attributes(toggle, active=0) self.treeview15.append_column(column_toggle) renderer_pix = gtk.CellRendererPixbuf() - column_pix = gtk.TreeViewColumn("Preview", renderer_pix, pixbuf=1) + column_pix = gtk.TreeViewColumn(_("Preview"), renderer_pix, pixbuf=1) self.treeview15.append_column(column_pix) cell = gtk.CellRendererText() column_item = gtk.TreeViewColumn("Filename", cell, text=2) @@ -736,28 +752,28 @@ cell = gtk.CellRendererText() cell.set_property("editable", True) cell.connect("edited", self.on_custom_model_edited, self.preset_model, 1) - column_item1 = gtk.TreeViewColumn("WB Name", cell, text=1) + column_item1 = gtk.TreeViewColumn(_("WB Name"), cell, text=1) column_item1.set_resizable(True) column_item1.set_expand(True) self.treeview16.append_column(column_item1) cell1 = gtk.CellRendererText() cell1.set_property("editable", True) cell1.connect("edited", self.on_custom_model_edited, self.preset_model, 2) - column_item2 = gtk.TreeViewColumn("Red multiplier", cell1, text=2) + column_item2 = gtk.TreeViewColumn(_("Red multiplier"), cell1, text=2) column_item2.set_resizable(True) column_item2.set_min_width(100) self.treeview16.append_column(column_item2) cell2 = gtk.CellRendererText() cell2.set_property("editable", True) cell2.connect("edited", self.on_custom_model_edited, self.preset_model, 3) - column_item3 = gtk.TreeViewColumn("Green multiplier", cell2, text=3) + column_item3 = gtk.TreeViewColumn(_("Green multiplier"), cell2, text=3) column_item3.set_resizable(True) column_item3.set_min_width(100) self.treeview16.append_column(column_item3) cell3 = gtk.CellRendererText() cell3.set_property("editable", True) cell3.connect("edited", self.on_custom_model_edited, self.preset_model, 4) - column_item4 = gtk.TreeViewColumn("Blue multiplier", cell3, text=4) + column_item4 = gtk.TreeViewColumn(_("Blue multiplier"), cell3, text=4) column_item4.set_resizable(True) column_item4.set_min_width(100) self.treeview16.append_column(column_item4) @@ -771,7 +787,7 @@ column_item.set_resizable(True) self.treeview17.append_column(column_item) cell = gtk.CellRendererText() - column_item = gtk.TreeViewColumn("workflow history", cell, text=1) + column_item = gtk.TreeViewColumn(_("workflow history"), cell, text=1) column_item.set_resizable(True) self.treeview17.append_column(column_item) self.treeview17.set_model(self.history_model) @@ -782,7 +798,7 @@ column_pix = gtk.TreeViewColumn("", renderer_pix, pixbuf=0) self.treeview18.append_column(column_pix) cell = gtk.CellRendererText() - column = gtk.TreeViewColumn("Tags", cell, text=1) + column = gtk.TreeViewColumn(_("Tags"), cell, text=1) self.treeview18.append_column(column) self.treeview18.set_model(self.tagmodel4) self.treeview18.enable_model_drag_dest([("text/plain", gtk.TARGET_SAME_APP, 81)], gtk.gdk.ACTION_COPY) @@ -1046,10 +1062,8 @@ self.imagemenuitem16.set_image(imagemenuitem16) menuitem19 = gtk.image_new_from_stock('dropbox', 1) self.menuitem19.set_image(menuitem19) - menuitem20 = gtk.image_new_from_stock('dropbox', 1) self.menuitem20.set_image(menuitem20) - menuitem56 = gtk.image_new_from_stock('picasa', 1) self.menuitem56.set_image(menuitem56) menuitem32 = gtk.image_new_from_file(self.get_icon_path('stock_contact.png')) @@ -1128,11 +1142,11 @@ self.hpaned2.set_position(int(screen1.get_width() * 0.6)) self.hpaned2.handler_unblock(self.id22) self.hpaned1.set_position(int(self.screen0.get_width() * 0.20)) - self.filter0.set_name('Sound files') - self.filter1.set_name('All known images') - self.filter2.set_name('ICC Profile') - self.filter3.set_name('GDKPixbuf known images') - self.filter4.set_name('Raw images') + self.filter0.set_name(_('Sound files')) + self.filter1.set_name(_('All known images')) + self.filter2.set_name(_('ICC Profile')) + self.filter3.set_name(_('GDKPixbuf known images')) + self.filter4.set_name(_('Raw images')) self.combobox1.handler_block(self.id50) self.combobox1.set_active(0) self.combobox1.handler_unblock(self.id50) @@ -1227,9 +1241,9 @@ self.menutoolbutton1.set_menu(self.menu18) self.menutoolbutton2.set_menu(self.menu19) buffer8 = self.textview8.get_buffer() - buffer8.set_text('\n'.join(self.formats)) + buffer8.set_text('\n'.join(sorted(self.formats))) buffer9 = self.textview9.get_buffer() - buffer9.set_text('\n'.join(self.raw_formats)) + buffer9.set_text('\n'.join(sorted(self.raw_formats))) self.combobox41.handler_block(self.id111) self.combobox41.set_active(0) self.combobox41.handler_unblock(self.id111) @@ -1249,7 +1263,7 @@ def load_batch_database(self): if os.path.isfile(os.path.join(self.ark, 'batch.db')): - f = open(os.path.join(self.ark, 'batch.db')) + f = open(os.path.join(self.ark, 'batch.db'), 'rb') self.batch_list = cPickle.load(f) f.close() self.batch_queue = self.batch_list[1] @@ -1261,7 +1275,7 @@ def load_mtimes_database(self): if os.path.isfile(os.path.join(self.ark, 'thumbs_mtimes.db')): - f = open(os.path.join(self.ark, 'thumbs_mtimes.db')) + f = open(os.path.join(self.ark, 'thumbs_mtimes.db'), 'rb') self.mtimes = cPickle.load(f) f.close() else: @@ -1269,13 +1283,13 @@ def load_wlist_database(self): if os.path.isfile(os.path.join(gtkrg_datadir, 'wlist.db')): - f = open(os.path.join(gtkrg_datadir, 'wlist.db')) + f = open(os.path.join(gtkrg_datadir, 'wlist.db'), 'rb') self.metadata_wlist = cPickle.load(f) f.close() def load_templates(self): if os.path.isfile(os.path.join(self.ark, 'templates.db')): - f = open(os.path.join(self.ark, 'templates.db')) + f = open(os.path.join(self.ark, 'templates.db'), 'rb') self.templates = cPickle.load(f) f.close() else: @@ -1379,7 +1393,7 @@ self.checkbutton33.set_active(True) if self.settings.has_key('checkbutton360'): self.checkbutton360.set_active(self.settings['checkbutton360']) - self.checkbutton36.set_sensitive(self.settings['checkbutton360']) + self.checkbutton36.set_sensitive(self.settings.get('checkbutton360') or False) if self.settings.has_key('checkbutton36'): self.checkbutton36.set_active(self.settings['checkbutton36']) if self.settings.has_key('checkbutton45'): @@ -1598,7 +1612,6 @@ self.combobox36.handler_unblock(self.id104) self.combobox37.handler_unblock(self.id105) self.combobox38.handler_unblock(self.id106) - else: self.settings = {} self.default_settings() @@ -1713,7 +1726,7 @@ self.populate_tag_managers() self.settings['raw_formats'] = self.raw_formats = RAW_FORMATS buffer9 = self.textview9.get_buffer() - buffer9.set_text('\n'.join(self.raw_formats)) + buffer9.set_text('\n'.join(sorted(self.raw_formats))) self.textview9.scroll_to_mark(buffer9.get_insert(), 0.1) self.connect_dcraw_gui() self.store_settings_db() @@ -1745,7 +1758,7 @@ def load_album_database(self): if os.path.isfile(os.path.join(self.ark, 'albums.db')): - f = open(os.path.join(self.ark, 'albums.db')) + f = open(os.path.join(self.ark, 'albums.db'), 'rb') self.albums = cPickle.load(f) f.close() self.boxmodel3.append(['All Albums']) @@ -2535,6 +2548,11 @@ self.menuitem9.connect('activate', self.on_menuitem9_activate) self.menuitem10.connect('activate', self.on_menuitem10_activate) self.menuitem18.connect('activate', self.on_menuitem18_activate) + self.colorbutton7.connect('color_set', self.on_colorbutton7_color_set) + + def on_colorbutton7_color_set(self, obj): + if self.drawing_area: + self.drawing_area.queue_draw() def edit_dcraw_extensions(self, obj): text = self.entry15.get_text().lower() @@ -2552,7 +2570,7 @@ raws.remove(text) self.raw_formats = tuple(raws) buffer9 = self.textview9.get_buffer() - buffer9.set_text('\n'.join(self.raw_formats)) + buffer9.set_text('\n'.join(sorted(self.raw_formats))) self.textview9.scroll_to_mark(buffer9.get_insert(), 0.1) def publisher(self, obj, module): @@ -2577,6 +2595,7 @@ def show_dialog9(self): dialog9 = self.messagedialog(_("Info!"), _("No file selected!"), None, gtk.BUTTONS_CLOSE, self.window1, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, None, (230,120)) dialog9.set_keep_above(True) + dialog9.set_size_request(350,-1) gtk.gdk.beep() response = dialog9.run() if response == gtk.RESPONSE_CLOSE or response == gtk.RESPONSE_DELETE_EVENT: @@ -2645,13 +2664,13 @@ def show_profile_info(self, obj, data): try: if data == 'input': - title = '%s profile info' % self.input_profile_name + title = 'Profile info of %s' % self.input_profile_name m_name = self.input_profile_name[:] elif data == 'output': - title = '%s profile info' % self.output_profile_name + title = 'Profile info of %s' % self.output_profile_name m_name = self.output_profile_name[:] elif data == 'display': - title = '%s profile info' % self.display_profile_name + title = 'Profile info of %s' % self.display_profile_name m_name = self.display_profile_name[:] info = gtk.Builder() info.add_from_file(os.path.join(gtkrg_datadir, 'info.ui')) @@ -2978,7 +2997,7 @@ self.progressbar7.set_text('') self.progressbar7.set_fraction(0) self.progressbar7.hide() - self.label243.set_text(_('Total size: %f Mb') % (self.cd_size / 1000000.0)) + self.label243.set_text(_('Total size: %.3f Mb') % (self.cd_size / 1000000.0)) if self.radiobutton6.get_active(): self.unselect() self.selected = [] @@ -2993,7 +3012,7 @@ self.cd_size += os.path.getsize(model[path][2]) else: self.cd_size -= os.path.getsize(model[path][2]) - self.label243.set_text(_('Total size: %f Mb') % (self.cd_size / 1000000.0)) + self.label243.set_text(_('Total size: %.3f Mb') % (self.cd_size / 1000000.0)) def on_window12_delete_event(self, obj, event=None): self.window12.hide() @@ -3017,21 +3036,13 @@ return True def show_tagmanager_info2(self, obj): - text = '''This panel permits to edit a custom metadata list. - -1) select a group; -2) select a tag; -3) click "add to list"; -Repeat the previous three points to add other tags; -4) Double click on the value column to edit; -5) Click the "write metadata" button. - -You can save the list as template.''' + text = _('This panel permits to edit a custom metadata list.\n1) select a group;\n2) select a tag;\n3) click "add to list";\nRepeat the previous three points to add other tags;\n4) Double click on the value column to edit;\n5) Click the "write metadata" button.\nYou can save the list as template.') info = gtk.Builder() + info.set_translation_domain('gtkrawgallery') info.add_from_file(os.path.join(gtkrg_datadir, 'info.ui')) dialog = info.get_object('dialog1') textview = info.get_object('textview1') - dialog.set_title('About Advanced Metadata Editor') + dialog.set_title(_('About Advanced Metadata Editor')) textview.get_buffer().set_text(text) dialog.set_transient_for(self.window2) dialog.show() @@ -3218,7 +3229,7 @@ gtk.main_iteration(False) self.progressbar6.hide() self.progressbar6.set_fraction(0) - self.label239.set_text(_('Total size: %f Mb') % (self.mail_size / 1000000.0)) + self.label239.set_text(_('Total size: %.3f Mb') % (self.mail_size / 1000000.0)) else: self.show_dialog9() @@ -3429,13 +3440,13 @@ self.aboutdialog.set_version(__VERSION__) self.aboutdialog.set_copyright(__COPYRIGHT__) self.aboutdialog.set_license(license_) - self.aboutdialog.set_comments('A workflow oriented photo manager\nfor digital camera raw image development.') + self.aboutdialog.set_comments(_('A photo manager\nfor digital camera raw image development.')) self.aboutdialog.set_authors(['Daniele Isca email: ']) gtk.about_dialog_set_url_hook(launch_browser) self.aboutdialog.set_website('http://sourceforge.net/projects/gtkrawgallery') - self.aboutdialog.set_website_label('Visit the Project Page') + self.aboutdialog.set_website_label(_('Visit the Project Page')) self.aboutdialog.set_logo(gtk.gdk.pixbuf_new_from_file_at_size(self.get_icon_path('splash.png'), 120, 90)) - self.aboutdialog.set_size_request(420,300) + self.aboutdialog.set_size_request(500,300) self.aboutdialog.set_position(gtk.WIN_POS_CENTER) def on_togglebutton6_toggled(self, obj): @@ -3937,25 +3948,26 @@ self.image28.show() def show_overexposed_pixels(self, obj, event, data): - self.overexposed = True - unexposed = self.image28.get_pixbuf() - wand = self.pixbuf2wand(unexposed) - if data == 'over': - wand.opaque_paint(Color('white'), Color('black'), 0) - else: - wand.opaque_paint(Color('black'), Color('white'), 0) - pixbuf = self.wand2pixbuf(wand, True) - while self.overexposed: - if not self.overexposed: - break - self.image28.set_from_pixbuf(pixbuf) - while gtk.events_pending(): - gtk.main_iteration(False) - time.sleep(0.5) - self.image28.set_from_pixbuf(unexposed) - while gtk.events_pending(): - gtk.main_iteration(False) - time.sleep(0.5) + if not self.expander17.get_expanded(): + self.overexposed = True + unexposed = self.image28.get_pixbuf() + wand = self.pixbuf2wand(unexposed) + if data == 'over': + wand.opaque_paint(Color('white'), Color('black'), 0) + else: + wand.opaque_paint(Color('black'), Color('white'), 0) + pixbuf = self.wand2pixbuf(wand, True) + while self.overexposed: + if not self.overexposed: + break + self.image28.set_from_pixbuf(pixbuf) + while gtk.events_pending(): + gtk.main_iteration(False) + time.sleep(0.5) + self.image28.set_from_pixbuf(unexposed) + while gtk.events_pending(): + gtk.main_iteration(False) + time.sleep(0.5) def on_exposure_buttons_left(self, obj, event): self.overexposed = False @@ -4337,7 +4349,7 @@ self.wand_index = 0 self.magick_size = 0 self.size_history = [0] * 100 - self.workflow_history = ['original'] + self.workflow_history = [_('Original')] self.history_model.clear() self.set_toolbuttons_23_39_sensitivity() self.combobox43.handler_block(self.id113) @@ -4563,7 +4575,7 @@ else: grayscale = False self.history.append(['recolor', self.matrix[:], grayscale]) - self.workflow_history.append('channel mixer') + self.workflow_history.append(_('Channel mixer')) self.update_history_model() self.image_was_changed = False self.checkbutton26.handler_block(self.id80) @@ -4741,7 +4753,7 @@ channel_type = {0 : api.AllChannels, 1 : api.RedChannel, 2 : api.GreenChannel, 3 : api.BlueChannel} for item in self.level_hist: self.history.append(['adjust_levels', (item[1][0] * Q_RANGE / 100, item[1][1], item[1][2] * Q_RANGE / 100, channel_type[item[0]])]) - self.workflow_history.append('levels') + self.workflow_history.append(_('Levels')) self.update_history_model() self.level_hist = [] self.reset_black_level(self.button18, False) @@ -4757,7 +4769,7 @@ return True self.update_wand_list() self.history.append(['modulate', (100 + self.bright_sat[0], 100 + self.bright_sat[1], 100 + self.bright_sat[2])]) - self.workflow_history.append('brightness and saturation') + self.workflow_history.append(_('Brightness and saturation')) self.update_history_model() while gtk.events_pending(): gtk.main_iteration(False) @@ -4777,7 +4789,7 @@ self.history.append([['evaluate', ('Multiply', 1 + self.c_balance[0] / 100, api.RedChannel)],\ ['evaluate', ('Multiply', 1 + self.c_balance[1] / 100, api.GreenChannel)],\ ['evaluate', ('Multiply', 1 + self.c_balance[2] / 100, api.BlueChannel)]]) - self.workflow_history.append('color balance') + self.workflow_history.append(_('Color balance')) self.update_history_model() while gtk.events_pending(): gtk.main_iteration(False) @@ -4793,7 +4805,7 @@ return True self.update_wand_list() self.history.append(['adjust_levels', (self.contrast * Q_RANGE / 100, 1.0, Q_RANGE - self.contrast * Q_RANGE / 100)]) - self.workflow_history.append('contrast') + self.workflow_history.append(_('Contrast')) self.update_history_model() while gtk.events_pending(): gtk.main_iteration(False) @@ -4810,7 +4822,7 @@ return True self.update_wand_list() self.history.append(['sigmoidal_contrast', self.sigmoidal_contrast_args][:]) - self.workflow_history.append('sigmoidal contrast') + self.workflow_history.append(_('Sigmoidal contrast')) self.update_history_model() self.reset_midtones_contrast(self.button46, False) self.image_was_changed = False @@ -4937,9 +4949,9 @@ elif item == self.expander25: self.save_despeckle() elif item == self.expander26: - self.save_curve('highlights') + self.save_curve(_('highlights')) elif item == self.expander27: - self.save_curve('shadows') + self.save_curve(_('shadows')) elif item == self.expander29: self.save_gamma_curve() elif item == self.expander30: @@ -4991,8 +5003,10 @@ self.wand = MagickImage(image_copy) self.pblist[self.pbindex] = self.wand2pixbuf(self.wand, True) if self.expander17.get_expanded(): - self.set_cairo_pixbuf() - self.x, self.y = self.x1, self.y1 +## self.set_cairo_pixbuf() +## self.x, self.y = self.x1, self.y1 + ct = self.set_cairo_pixbuf() + self.draw_rectangle_with_ratio(ct) else: if self.checkbutton41.get_active(): self.image28.set_from_pixbuf(self.profile_pixbuf(self.pblist[self.pbindex])) @@ -5030,8 +5044,8 @@ self.wand = MagickImage(image_copy) self.pblist[self.pbindex] = self.wand2pixbuf(self.wand, True) if self.expander17.get_expanded(): - self.set_cairo_pixbuf() - self.x, self.y = self.x1, self.y1 + ct = self.set_cairo_pixbuf() + self.draw_rectangle_with_ratio(ct) else: if self.checkbutton41.get_active(): self.image28.set_from_pixbuf(self.profile_pixbuf(self.pblist[self.pbindex])) @@ -5132,7 +5146,7 @@ if not self.workflow_started: self.original_pixbuf = self.pblist[self.pbindex].copy()#### self.history = [['style', history]] - self.workflow_history = ['original', 'style ' + style_name] + self.workflow_history = [_('Original'), _('Style ') + style_name] self.update_history_model() self.workflow_started = True self.wand_index = 1 @@ -5582,8 +5596,8 @@ self.wand = MagickImage(self.wand_list[self.wand_index].get_image()) self.pblist[self.pbindex] = self.wand2pixbuf(self.wand, True) if self.expander17.get_expanded(): - self.set_cairo_pixbuf() - self.x, self.y = self.x1, self.y1 + ct = self.set_cairo_pixbuf() + self.draw_rectangle_with_ratio(ct) elif self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) elif self.expander19.get_expanded(): @@ -5646,6 +5660,10 @@ gobject.idle_add(self.image28.show) def make_drawingarea(self): + self.x = 0 + self.y = 0 + self.crop_cursor = 0 + self.crop_cursor_enable = False self.set_zoom_sensitivity(False) self.block_viewport1_signals() self.zoom_fit_photo(None, False) @@ -5653,13 +5671,277 @@ self.drawing_area = gtk.DrawingArea() self.viewport1.add(self.drawing_area) self.drawing_area.modify_bg(gtk.STATE_NORMAL, self.image_background_color) - self.drawing_area.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON1_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK) - self.drawing_area.connect('expose_event', self.on_drawing_area_expose_event, False) + self.viewport1.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK) + self.viewport1.connect('motion_notify_event', self.on_vp1_motion_notify_event) + self.drawing_area.add_events(gtk.gdk.ALL_EVENTS_MASK) + self.drawing_area.connect('expose_event', self.on_drawing_area_expose_event, True) self.drawing_area.connect('size_allocate', self.on_drawing_area_size_allocate) self.drawing_area.connect('button_press_event', self.on_drawing_area_button_press_event) - self.combobox12_id = self.combobox12.connect('changed', self.on_drawing_area_expose_event, self.drawing_area, True) + self.drawing_area.connect('button_release_event', self.on_drawing_area_button_release_event) + self.combobox12_id = self.combobox12.connect('changed', self.on_combobox12_changed) + self.drawing_area.connect('motion_notify_event', self.on_drawing_area_motion_notify_event) self.drawing_area.show() + + def on_drawing_area_motion_notify_event(self, obj, event): + if self.crop_cursor_enable: + if self.crop_cursor == 9: + self.drag_crop_selection(obj, event) + elif self.crop_cursor == 1: + self.drag_bottom_left(obj, event) + elif self.crop_cursor == 2: + self.drag_top(obj, event) + elif self.crop_cursor == 3: + self.drag_bottom_right(obj, event) + elif self.crop_cursor == 4: + self.drag_right(obj, event) + elif self.crop_cursor == 5: + self.drag_bottom_right(obj, event) + elif self.crop_cursor == 6: + self.drag_bottom(obj, event) + elif self.crop_cursor == 7: + self.drag_bottom_left(obj, event) + elif self.crop_cursor == 8: + self.drag_left(obj, event) + + def drag_crop_selection(self, obj, event): + w = self.x1 - self.x + h = self.y1 - self.y + self.x += event.x - self.x0 + self.y += event.y - self.y0 + self.x1 += event.x - self.x0 + self.y1 += event.y - self.y0 + self.x0, self.y0 = event.x, event.y + ct = obj.window.cairo_create() + ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) + ct.paint() + ct.rectangle(self.x_offset, self.y_offset,self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() + ct.set_source_color(self._color) + ct.set_line_width(0.5) + ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) + ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + def drag_right(self, obj, event): + if self.crop_ratio == 0: + w = event.x - self.x + h = self.y1 - self.y + self.x1 = event.x + else: + dx = event.x - self.x1 + dy = int(dx/self.crop_ratio) + self.y -= dy + self.x -= dx + w = self.x1 + dx - self.x + h = int(w/self.crop_ratio) + self.x1 = self.x + w + self.y1 = self.y + h + ct = obj.window.cairo_create() + ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) + ct.paint() + ct.rectangle(self.x_offset, self.y_offset,self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() + ct.set_source_color(self._color) + ct.set_line_width(0.5) + ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) + ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + + def drag_left(self, obj, event): + if self.crop_ratio == 0: + w = self.x1 - event.x + h = self.y1 - self.y + self.x = event.x + else: + dx = self.x - event.x + dy = int(dx/self.crop_ratio) + self.y -= dy + self.x -= dx + w = self.x1 + dx - self.x + h = int(w/self.crop_ratio) + self.x1 = self.x + w + self.y1 = self.y + h + ct = obj.window.cairo_create() + ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) + ct.paint() + ct.rectangle(self.x_offset, self.y_offset,self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() + ct.set_source_color(self._color) + ct.set_line_width(0.5) + ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) + ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + + def drag_bottom(self, obj, event): + if self.crop_ratio == 0: + h = event.y - self.y + w = self.x1 - self.x + self.y1 = event.y + else: + dy = event.y - self.y1 + dx = int(dy * self.crop_ratio) + self.y -= dy + self.x -= dx + w = self.x1 + dx - self.x + h = int(w/self.crop_ratio) + self.x1 = self.x + w + self.y1 = self.y + h + ct = obj.window.cairo_create() + ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) + ct.paint() + ct.rectangle(self.x_offset, self.y_offset,self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() + ct.set_source_color(self._color) + ct.set_line_width(0.5) + ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) + ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + + def drag_top(self, obj, event): + if self.crop_ratio == 0: + h = self.y1 - event.y + w = self.x1 - self.x + self.y = event.y + else: + dy = self.y - event.y + dx = int(dy * self.crop_ratio) + self.y -= dy + self.x -= dx + w = self.x1 + dx - self.x + h = int(w/self.crop_ratio) + self.x1 = self.x + w + self.y1 = self.y + h + ct = obj.window.cairo_create() + ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) + ct.paint() + ct.rectangle(self.x_offset, self.y_offset,self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() + ct.set_source_color(self._color) + ct.set_line_width(0.5) + ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) + ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + + def drag_bottom_right(self, obj, event): + if self.crop_ratio == 0: + self.x1, self.y1 = event.x, event.y + w = self.x1 - self.x + h = self.y1 -self.y + else: + dx = event.x - self.x1 + dy = int(dx / self.crop_ratio) + self.y -= dy + self.x -= dx + w = self.x1 + dx - self.x + h = int(w / self.crop_ratio) + self.x1 = self.x + w + self.y1 = self.y + h + ct = obj.window.cairo_create() + ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) + ct.paint() + ct.rectangle(self.x_offset, self.y_offset,self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() + ct.set_source_color(self._color) + ct.set_line_width(0.5) + ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) + ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + + def drag_bottom_left(self, obj, event): + if self.crop_ratio == 0: + self.x, self.y = event.x, event.y + w = self.x1 - self.x + h = self.y1 -self.y + else: + dx = self.x - event.x + dy = int(dx / self.crop_ratio) + self.y -= dy + self.x -= dx + w = self.x1 + dx - self.x + h = int(w / self.crop_ratio) + self.x1 = self.x + w + self.y1 = self.y + h + ct = obj.window.cairo_create() + ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) + ct.paint() + ct.rectangle(self.x_offset, self.y_offset,self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() + ct.set_source_color(self._color) + ct.set_line_width(0.5) + ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) + ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + def on_drawing_area_size_allocate(self, obj, allocation): self.rect = self.viewport1.allocation width = self.pblist[self.pbindex].get_width() @@ -5668,14 +5950,17 @@ self.drawing_area.set_size_request(width, height) def on_drawing_area_expose_event(self, obj, event, data): - if self._id and self.drawing_area.handler_is_connected(self._id): - self.drawing_area.disconnect(self._id) - self._id = self.drawing_area.connect('motion_notify_event', self.on_drawing_area_motion_notify_event,\ - self.pblist[self.pbindex].get_width(), self.pblist[self.pbindex].get_height()) self._color = self.colorbutton7.get_color() ct = self.set_cairo_pixbuf() if data: self.draw_rectangle_with_ratio(ct) + + def on_combobox12_changed(self, obj): + self.x = 0 + self.y = 0 + self._color = self.colorbutton7.get_color() + ct = self.set_cairo_pixbuf() + self.draw_rectangle_with_ratio(ct) def set_cairo_pixbuf(self): self.on_drawing_area_size_allocate(self.drawing_area, self.drawing_area.allocation) @@ -5686,108 +5971,148 @@ self.current_pixbuf = self.pblist[self.pbindex] ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) ct.paint() + ct.rectangle(self.x_offset, self.y_offset, self.current_pixbuf.get_width(), self.current_pixbuf.get_height()) + ct.clip() return ct def draw_rectangle_with_ratio(self, ct): width = self.pblist[self.pbindex].get_width() height = self.pblist[self.pbindex].get_height() - if not self.checkbutton21.get_active(): - ratio = self.combobox12.get_active() - if ratio == 0: - self.crop_ratio = 1 - h = w = min(width / 2, height / 2) - elif ratio == 1: - self.crop_ratio = 3 / 2.0 - w = min(width / 2, height / 2) - h = w / self.crop_ratio - elif ratio == 2: - self.crop_ratio = 2 / 3.0 - h = min(width / 2, height / 2) - w = h * self.crop_ratio - elif ratio == 3: - self.crop_ratio = 4 / 3.0 - w = min(width / 2, height / 2) - h = w / self.crop_ratio - elif ratio == 4: - self.crop_ratio = 3 / 4.0 - h = min(width / 2, height / 2) - w = h * self.crop_ratio - elif ratio == 5: - self.crop_ratio = 5 / 4.0 - w = min(width / 2, height / 2) - h = w / self.crop_ratio - elif ratio == 6: - self.crop_ratio = 4 / 5.0 - h = min(width / 2, height / 2) - w = h * self.crop_ratio - elif ratio == 7: - self.crop_ratio = 7 / 5.0 - w = min(width / 2, height / 2) - h = w / self.crop_ratio - elif ratio == 8: - self.crop_ratio = 5 / 7.0 - h = min(width / 2, height / 2) - w = h * self.crop_ratio - elif ratio == 9: - self.crop_ratio = 3 / 1.0 - w = min(width / 2, height / 2) - h = w / self.crop_ratio - elif ratio == 10: - self.crop_ratio = 1 / 3.0 - h = min(width / 2, height / 2) - w = h * self.crop_ratio - elif ratio == 11: - self.crop_ratio = 0 - w = width / 2 - h = height / 2 - else: - self.crop_ratio = self.spinbutton30.get_value() / self.spinbutton38.get_value() - if self.crop_ratio > 1: - w = min(width / 2, height / 2) - h = w / self.crop_ratio - else: - h = min(width / 2, height / 2) - w = h * self.crop_ratio - self.x, self.y = int(self.rect.width / 2 - w / 2), int(self.rect.height / 2 - h / 2) - self.x1, self.y1 = self.x + int(w), self.y + int(h) + if not self.x and not self.y: + if not self.checkbutton21.get_active(): + ratio = self.combobox12.get_active() + if ratio == 0: + self.crop_ratio = 1 + h = w = min(width / 2, height / 2) + elif ratio == 1: + self.crop_ratio = 3 / 2.0 + w = min(width / 2, height / 2) + h = w / self.crop_ratio + elif ratio == 2: + self.crop_ratio = 2 / 3.0 + h = min(width / 2, height / 2) + w = h * self.crop_ratio + elif ratio == 3: + self.crop_ratio = 4 / 3.0 + w = min(width / 2, height / 2) + h = w / self.crop_ratio + elif ratio == 4: + self.crop_ratio = 3 / 4.0 + h = min(width / 2, height / 2) + w = h * self.crop_ratio + elif ratio == 5: + self.crop_ratio = 5 / 4.0 + w = min(width / 2, height / 2) + h = w / self.crop_ratio + elif ratio == 6: + self.crop_ratio = 4 / 5.0 + h = min(width / 2, height / 2) + w = h * self.crop_ratio + elif ratio == 7: + self.crop_ratio = 7 / 5.0 + w = min(width / 2, height / 2) + h = w / self.crop_ratio + elif ratio == 8: + self.crop_ratio = 5 / 7.0 + h = min(width / 2, height / 2) + w = h * self.crop_ratio + elif ratio == 9: + self.crop_ratio = 3 / 1.0 + w = min(width / 2, height / 2) + h = w / self.crop_ratio + elif ratio == 10: + self.crop_ratio = 1 / 3.0 + h = min(width / 2, height / 2) + w = h * self.crop_ratio + elif ratio == 11: + self.crop_ratio = 0 + w = width / 2 + h = height / 2 + else: + self.crop_ratio = self.spinbutton30.get_value() / self.spinbutton38.get_value() + if self.crop_ratio > 1: + w = min(width / 2, height / 2) + h = w / self.crop_ratio + else: + h = min(width / 2, height / 2) + w = h * self.crop_ratio + self.x, self.y = int(self.rect.width / 2 - w / 2), int(self.rect.height / 2 - h / 2) + self.x1, self.y1 = self.x + int(w), self.y + int(h) + else: + w = self.x1 - self.x + h = self.y1 - self.y ct.set_source_color(self._color) + ct.set_line_width(0.5) ct.rectangle(self.x, self.y, w, h) + ct.move_to(self.x+w/3.0, self.y) + ct.line_to(self.x+w/3.0, self.y1) + ct.move_to(self.x+w*2/3.0, self.y) + ct.line_to(self.x+w*2/3.0, self.y1) + ct.move_to(self.x, self.y+h/3.0) + ct.line_to(self.x1, self.y+h/3.0) + ct.move_to(self.x, self.y+h*2/3.0) + ct.line_to(self.x1, self.y+h*2/3.0) ct.stroke() + ct.set_source_rgba(0,0,0,0.4) + ct.rectangle(self.x, self.y, w, h) + ct.fill() + + def on_drawing_area_button_release_event(self, obj, event): + if event.button == 1: + self.crop_cursor_enable = False def on_drawing_area_button_press_event(self, obj, event): if event.button == 1: - width = self.pblist[self.pbindex].get_width() - height = self.pblist[self.pbindex].get_height() - self.rect = self.viewport1.allocation - self.x_offset, self.y_offset = int((self.rect.width - width) / 2.0), int((self.rect.height - height) / 2.0) - x0, x1, y0, y1 = (self.rect.width - width) / 2.0, self.rect.width - (self.rect.width - width) / 2.0,\ - (self.rect.height - height) / 2.0, self.rect.height - (self.rect.height - height) / 2.0 - self.x, self.y = self.x1, self.y1 = event.window.get_pointer()[:2] - ct = self.set_cairo_pixbuf() - if x0 < self.x < x1 and y0 < self.y < y1 : - ct.set_source_color(self._color) - ct.move_to(x0, event.y) - ct.line_to(x1, event.y) - ct.move_to(event.x, y0) - ct.line_to(event.x, y1) - ct.stroke() - - def on_drawing_area_motion_notify_event(self, obj, event, width, height): - if event.x > self.x and event.y > self.y: - if self.crop_ratio: - if (event.x - self.x) / (event.y - self.y) > self.crop_ratio: - event.x = self.crop_ratio * (event.y - self.y) + self.x - else: - event.y = (1 / self.crop_ratio) * (event.x - self.x) + self.y - ct = obj.window.cairo_create() - ct.rectangle(self.x_offset, self.y_offset, width, height) - ct.clip() - ct.set_source_pixbuf(self.current_pixbuf, self.x_offset, self.y_offset) - ct.paint() - ct.set_source_color(self._color) - ct.rectangle(self.x, self.y, event.x - self.x, event.y - self.y) - ct.stroke() - self.x1, self.y1 = event.x, event.y + self.crop_cursor_enable = True + self.x0, self.y0 = event.window.get_pointer()[:2] + + def on_vp1_motion_notify_event(self, obj, event): + if self.x-3 < event.x < self.x+3: + if self.y+7 < event.y < self.y1-7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW)) + self.crop_cursor = 8 + elif self.y-7 < event.y < self.y+7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_CORNER)) + self.crop_cursor = 1 + elif self.y1-7 < event.y < self.y1+7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.BOTTOM_LEFT_CORNER)) + self.crop_cursor = 7 + else: + event.window.set_cursor(None) + self.crop_cursor = 0 + elif self.x1-7 < event.x < self.x1+7: + if self.y+7 < event.y < self.y1-7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW)) + self.crop_cursor = 4 + elif self.y-7 < event.y < self.y+7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_RIGHT_CORNER)) + self.crop_cursor = 3 + elif self.y1-7 < event.y < self.y1+7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.BOTTOM_RIGHT_CORNER)) + self.crop_cursor = 5 + else: + event.window.set_cursor(None) + self.crop_cursor = 0 + elif self.y-7 < event.y < self.y+7: + if self.x+7 < event.x < self.x1-7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_V_DOUBLE_ARROW)) + self.crop_cursor = 2 + else: + event.window.set_cursor(None) + self.crop_cursor = 0 + elif self.y1-7 < event.y < self.y1+7: + if self.x+7 < event.x < self.x1-7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_V_DOUBLE_ARROW)) + self.crop_cursor = 6 + else: + event.window.set_cursor(None) + self.crop_cursor = 0 + elif self.x+7 < event.x < self.x1 -7 and self.y+7 < event.y < self.y1-7: + event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR)) + self.crop_cursor = 9 + else: + event.window.set_cursor(None) + self.crop_cursor = 0 def destroy_drawingarea(self): self.drawing_area.destroy() @@ -5833,8 +6158,8 @@ self.history.append(['thumbnail', self.scale_args[:]]) else: self.history.append(['scale', self.scale_args[:]]) - self.workflow_history.append('scale') - + self.workflow_history.append(_('Scale')) + self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) self.magick_size = [str(self.scale_args[0]), str(self.scale_args[1])] @@ -5853,15 +6178,19 @@ if data == 'flip': self.wand.flip() self.history.append(['flip',()]) + data = _('Flip') elif data == 'flop': self.wand.flop() self.history.append(['flop',()]) + data = _('Flop') elif data == 'rotate 90': self.wand.rotate(90.0, None) self.history.append(['rotate', (90.0, None)]) + data = _('Rotate 90') elif data == 'rotate -90': self.wand.rotate(-90.0, None) self.history.append(['rotate', (-90.0, None)]) + data = _('Rotate -90') self.update_wand_list() self.workflow_history.append(data) self.update_history_model() @@ -5898,7 +6227,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['border', self.IM_border_args[:]]) - self.workflow_history.append('border') + self.workflow_history.append(_('Border')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -5926,7 +6255,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['rotate', (self.fine_degree, None)]) - self.workflow_history.append('fine rotation') + self.workflow_history.append(_('Fine rotation')) self.update_history_model() def apply_crop(self,obj): @@ -5961,7 +6290,8 @@ if not self.workflow_started: self.original_pixbuf = self.pblist[self.pbindex].copy() self.pblist[self.pbindex] = self.wand2pixbuf(self.wand, True) - self.set_cairo_pixbuf() + ct = self.set_cairo_pixbuf() + self.draw_rectangle_with_ratio(ct) self.get_channel() self.image_was_changed = True self.workflow_started = True @@ -5981,9 +6311,9 @@ self.size_history[self.wand_index:] = [ crop_size for i in xrange(self.wand_index, 100)] self.image_was_changed = False self.history.append([['_get_size', None], ['crop', self.crop_args[:]], ['set_page', self.repage_args[:]]]) - self.workflow_history.append('crop') + self.workflow_history.append(_('Crop')) self.update_history_model() - self.x, self.y = self.x1, self.y1 + # self.x, self.y = self.x1, self.y1 def apply_effect(self, fun, args, reprocess=None): if reprocess: @@ -6057,7 +6387,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['sharpen', self.sharpen_args[:]]) - self.workflow_history.append('sharpen') + self.workflow_history.append(_('Sharpen')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6097,7 +6427,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['unsharp_mask', self.unsharp_args[:]]) - self.workflow_history.append('unsharp mask') + self.workflow_history.append(_('Unsharp mask')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6157,7 +6487,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append([self.blur_type, self.blur_args[:]]) - self.workflow_history.append('blur') + self.workflow_history.append(_('Blur')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6194,7 +6524,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['charcoal', self.charcoal_args[:]]) - self.workflow_history.append('charcoal') + self.workflow_history.append(_('Charcoal')) self.update_history_model() self.spinbutton65.set_value(1) self.spinbutton66.set_value(0) @@ -6234,7 +6564,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['sketch', self.sketch_args[:]]) - self.workflow_history.append('sketch') + self.workflow_history.append(_('Sketch')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6273,7 +6603,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['posterize', self.posterize_args[:]]) - self.workflow_history.append('posterize') + self.workflow_history.append(_('Posterize')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6311,7 +6641,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['negate', self.negate_args[:]]) - self.workflow_history.append('negate') + self.workflow_history.append(_('Negate')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6349,7 +6679,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['add_noise', self.add_noise_args[:]]) - self.workflow_history.append('add noise') + self.workflow_history.append(_('Add noise')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6386,7 +6716,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['reduce_noise', self.reduce_noise_args[:]]) - self.workflow_history.append('reduce noise') + self.workflow_history.append(_('Reduce noise')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6423,7 +6753,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['median_filter', self.median_args[:]]) - self.workflow_history.append('median filter') + self.workflow_history.append(_('Median filter')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6460,7 +6790,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['sepia_tone', self.sepia_tone_args[:]]) - self.workflow_history.append('sepia tone') + self.workflow_history.append(_('Sepia tone')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6487,7 +6817,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['_set_colorspace', (api.GRAYColorspace,)]) - self.workflow_history.append('grayscale') + self.workflow_history.append(_('Grayscale')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6520,7 +6850,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(self.vignette_args) - self.workflow_history.append('vignette removal') + self.workflow_history.append(_('Vignette removal')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6531,7 +6861,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['normalize', ()]) - self.workflow_history.append('normalize') + self.workflow_history.append(_('Normalize')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6562,7 +6892,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['distort', self.distort_args[:]]) - self.workflow_history.append('distort') + self.workflow_history.append(_('Distort')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6644,7 +6974,7 @@ else: self.update_wand_list() self.history.append(['red_eye', [(self.red_eye_args[:])]]) - self.workflow_history.append('red eye removal') + self.workflow_history.append(_('Red eye removal')) self.update_history_model() gobject.idle_add(self.set_toolbuttons_23_39_sensitivity) self.button114.set_sensitive(True) @@ -6684,7 +7014,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['oil_paint', self.oil_paint_args[:]]) - self.workflow_history.append('oil paint') + self.workflow_history.append(_('Oil paint')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6721,7 +7051,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['spread', self.spread_args[:]]) - self.workflow_history.append('spread') + self.workflow_history.append(_('Spread')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6755,7 +7085,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['despeckle', ()]) - self.workflow_history.append('despeckle') + self.workflow_history.append(_('Despeckle')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -6912,7 +7242,7 @@ curve_hist[1].append(['function', (self.vector[3][:], api.BlueChannel)]) if curve_hist[1]: self.history.append(curve_hist) - self.workflow_history.append('curve') + self.workflow_history.append(_('Curve')) self.update_history_model() if self.expander29.get_expanded(): self.curve1.reset() @@ -7029,9 +7359,9 @@ if obj == self.eventbox8: self.save_levels() elif obj == self.eventbox9: - self.save_curve('highlights') + self.save_curve(_('highlights')) elif obj == self.eventbox10: - self.save_curve('shadows') + self.save_curve(_('shadows')) elif obj == self.eventbox11: self.save_brightness_and_saturation() elif obj == self.eventbox12: @@ -7147,7 +7477,7 @@ self.image_was_changed = False self.button142.set_sensitive(False) self.history.append(['tint', (self.tint_color,)]) - self.workflow_history.append('tint') + self.workflow_history.append(_('Tint')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -7291,7 +7621,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(self.wb_hist[:]) - self.workflow_history.append('white balance') + self.workflow_history.append(_('White balance')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -7321,7 +7651,7 @@ self.update_wand_list() self.image_was_changed = False self.history.append(['recolor', self.matrix[:], False]) - self.workflow_history.append('color temperature') + self.workflow_history.append(_('Color temperature')) self.update_history_model() if self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -7588,7 +7918,7 @@ continue run = self.batchmodel.get_value(self.batch_iter, 0) selection = [row[2] for row in self.batchmodel if row[2] == True] - gobject.idle_add(self.window7.set_title, _('Batch Manager - processing file %s - remaining %s') % (run, max(0, len(selection)-1))) + gobject.idle_add(self.window7.set_title, _('Batch Manager - processing file %(run)s - remaining %(rem)s') % {'run':run, 'rem':max(0, len(selection)-1)}) gobject.idle_add(self.batchmodel.set_value, self.batch_iter, 4, 'processing...') process_options = self.get_process_options(infile, extension, dest) self.process(*process_options) @@ -7978,7 +8308,7 @@ def exiftool_warning(self, filename, output): if not self.checkbutton32.get_active(): - self.label174.set_text(_('Exiftool cannot write %s\n%s') % (os.path.basename(filename), output)) + self.label174.set_text(_('Exiftool cannot write %(name_)s\n%(output_)s') % {'name_':os.path.basename(filename), 'output_':output}) self.dialog30.show() response = self.dialog30.run() if response == gtk.RESPONSE_CLOSE or response == gtk.RESPONSE_DELETE_EVENT: @@ -8368,7 +8698,7 @@ while gtk.events_pending(): gtk.main_iteration(False) self.mail_size -= os.path.getsize(filename) - self.label239.set_text(_('Total size: %f Mb') % (self.mail_size / 1000000.0)) + self.label239.set_text(_('Total size: %.3f Mb') % (self.mail_size / 1000000.0)) elif obj == self.window12 and len(self.cdmodel): if keyname == 'Delete': selection = self.treeview15.get_selection() @@ -8379,7 +8709,7 @@ while gtk.events_pending(): gtk.main_iteration(False) self.cd_size -= os.path.getsize(filename) - self.label243.set_text(_('Total size: %f Mb') % (self.cd_size / 1000000.0)) + self.label243.set_text(_('Total size: %.3f Mb') % (self.cd_size / 1000000.0)) elif obj == self.window15 and len(self.preset_model): if keyname == 'Delete': self.remove_WB_row() @@ -8649,8 +8979,8 @@ self.settings['radiobutton7'] = not self.radiobutton6.get_active() archives = self.filechooserbutton1.get_current_folder() if archives != self.ark: - f = open(os.path.join(self.ark, 'albums.db'), 'w') - cPickle.dump(self.albums, f) + f = open(os.path.join(self.ark, 'albums.db'), 'wb') + cPickle.dump(self.albums, f, protocol=1) f.close() self.ark = archives self.settings['ark'] = archives @@ -8777,6 +9107,7 @@ if self.checkbutton41.get_active() and self.combobox38.get_active() == 2: text = self.get_profile_info(image=wand.get_filename()) self.label272.set_text(text) + self.dialog34.set_title(_('Embedded Profile')) self.dialog34.show() response = self.dialog34.run() if response == -6: @@ -9610,8 +9941,8 @@ self.load_embedded_profile(wand) self.pblist[self.pbindex] = self.wand2pixbuf(wand, True) if self.expander17.get_expanded(): - self.set_cairo_pixbuf() - self.x, self.y = self.x1, self.y1 + ct = self.set_cairo_pixbuf() + self.draw_rectangle_with_ratio(ct) self.set_zoom_sensitivity(False) elif self.expander8.get_expanded(): self.draw_grid(self.checkbutton24) @@ -9836,7 +10167,7 @@ if self.stop_thread.isSet(): break n += 1 - gobject.idle_add(self.window6.set_title, _('Converting %s of %s files...') % (n, len(selected))) + gobject.idle_add(self.window6.set_title, _('Converting %(n)s of %(sel)s files...') % {'n':n, 'sel':len(selected)}) format = os.path.splitext(item)[1] if format.lower() in self.raw_formats: filename = self.get_real_path(item) @@ -9885,7 +10216,7 @@ if self.stop_thread.isSet(): break n += 1 - gobject.idle_add(self.window6.set_title, _('Converting %s of %s files...') % (n, len(selected))) + gobject.idle_add(self.window6.set_title, _('Converting %(n)s of %(sel)s files...') % {'n':n, 'sel':len(selected)}) format = os.path.splitext(item)[1] if format.lower() in self.raw_formats: filename = self.get_real_path(item) @@ -9934,7 +10265,7 @@ if self.stop_thread.isSet(): break n += 1 - gobject.idle_add(self.window6.set_title, _('Converting %s of %s files...') % (n, len(selected))) + gobject.idle_add(self.window6.set_title, _('Converting %(n)s of %(sel)s files...') % {'n':n, 'sel':len(selected)}) format = os.path.splitext(item)[1] if format.lower() in self.raw_formats: filename = self.get_real_path(item) @@ -9983,7 +10314,7 @@ if self.stop_thread.isSet(): break n += 1 - gobject.idle_add(self.window6.set_title, _('Converting %s of %s files...') % (n, len(selected))) + gobject.idle_add(self.window6.set_title, _('Converting %(n)s of %(sel)s files...') % {'n':n, 'sel':len(selected)}) format = os.path.splitext(item)[1] if format.lower() in self.raw_formats: filename = self.get_real_path(item) @@ -10042,8 +10373,9 @@ '-ImageHeight', filename] output = self.exiftool.get_metadata('string', *list_) self.grep_image_size(json.loads(output)) + self.image_size = self.image_size.replace('px', '') full_size = self.image_size.split('x') - full_i = ((int(full_size[0]) ** 2 + int(full_size[1]) ** 2) ** 0.5) + full_i = ((float(full_size[0]) ** 2 + float(full_size[1]) ** 2) ** 0.5) text = '' if full_i: text = str(int((i / full_i) * 100)) + '%' @@ -10061,11 +10393,12 @@ '-ImageHeight', filename] output = self.exiftool.get_metadata('string', *list_) self.grep_image_size(json.loads(output)) + self.image_size = self.image_size.replace('px', '') full_size = self.image_size.split('x') - full_i = (int(full_size[0]) ** 2 + int(full_size[1]) ** 2) ** 0.5 + full_i = (float(full_size[0]) ** 2 + float(full_size[1]) ** 2) ** 0.5 text = '' if full_i: - text = str(int((i / full_i) * 100)) + '%' + text = str(int((i / full_i) * 100)) + _('% of ') + self.image_size self.statusbar_pop(self.statusbar4, self.context_id4) self.statusbar_push(self.statusbar4, self.context_id4, text) except: @@ -10127,11 +10460,10 @@ self.attach_images() self.make_thumbnails(self.width, self.height, self.n) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - 'There are %s images Total size %f MB' % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3, 'There are %s images' % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) if self.radiobutton7.get_active(): try: self.hbox1.destroy() @@ -10241,11 +10573,10 @@ self.attach_images() self.make_thumbnails(self.width, self.height, self.n) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - 'There are %s images Total size %f MB' % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3,_('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3, 'There are %s images' % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) if self.radiobutton7.get_active(): try: self.hbox1.destroy() @@ -10277,11 +10608,10 @@ self.attach_images() self.make_thumbnails(self.width, self.height, self.n) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - 'There are %s images Total size %f MB '% (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB ') % {'n':self.n, 'mb':self.total_size / 1000000.0}) else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3, 'There are %s images' % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %s images')) self.add_selection1.set_sensitive(True) self.selected = [] self.settings['last_folder_path'] = self.cartella[:] @@ -10411,8 +10741,7 @@ self.attach_images() self.make_thumbnails(self.width, self.height, self.n) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) else: if self.radiobutton7.get_active(): try: @@ -10828,8 +11157,8 @@ if self.checkbutton4.get_active() and width > 10 and height > 10: pixbuf = pixbuf.subpixbuf(2, 2, width - 4, height - 4) ratio = float(width) / height - if ratio >= 1: - pixbuf = pixbuf.scale_simple(self.width, int(self.width / ratio), gtk.gdk.INTERP_BILINEAR) + if ratio == 1: + pixbuf = pixbuf.scale_simple(self.width, self.width, gtk.gdk.INTERP_BILINEAR) else: pixbuf = pixbuf.scale_simple(int(self.height * ratio), self.height, gtk.gdk.INTERP_BILINEAR) if self.checkbutton4.get_active(): @@ -10915,10 +11244,7 @@ self.open_on_window2() def sox(self): - if sys.platform.startswith("win") and self.sample.lower().endswith(".mp3"): - list_ = ["madplay", "-r", self.sample] - else: - list_ = ["sox", "-q", self.sample, "-d", "repeat", "50"] + list_ = ["sox", "-q", self.sample, "-d", "repeat", "50"] p = Popen(list_, stdin=PIPE, stdout=PIPE, stderr=STDOUT, creationflags = CREATIONFLAGS) self.pid = p.pid p.stdout.close() @@ -10943,9 +11269,20 @@ def pause_slideshow(self, obj): self.loop = 2 + if self.checkbutton6.get_active() and self.pid: + try: + self.kill_process(self.pid) + self.pid = 0 + except: + gobject.idle_add(sys.stderr.write, traceback.format_exc()) def start_slideshow(self, obj): self.loop = 1 + if not self.pid and self.checkbutton6.get_active() and self.sample: + try: + self.sox() + except: + gobject.idle_add(sys.stderr.write, traceback.format_exc()) self.slideshow(None) def slideshow(self, *data): @@ -11111,7 +11448,7 @@ self.pblist[1] = self.load_cache_pixbuf(self.list[self.indice], self.rect.height, self.rect.width) self.pblist[1] = self.pblist[1].rotate_simple(self.rotated[self.indice]) else: - if self.checkbutton41.get_active(): + if self.checkbutton41.get_active() and format.lower() != '.svg': wand = MagickImage(self.list[self.indice]) self.load_embedded_profile(wand) self.pblist[1] = self.wand2pixbuf(wand, True) @@ -11715,8 +12052,8 @@ elif self.expander19.get_expanded(): self.draw_grid(self.checkbutton34) elif self.expander17.get_expanded(): - self.set_cairo_pixbuf() - self.x, self.y = self.x1, self.y1 + ct = self.set_cairo_pixbuf() + self.draw_rectangle_with_ratio(ct) else: if self.checkbutton41.get_active(): self.image28.set_from_pixbuf(self.profile_pixbuf(self.pblist[self.pbindex])) @@ -12506,8 +12843,7 @@ self.make_table() self.attach_images() self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') %(self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) if self.radiobutton7.get_active(): self.make_bottom_panel(None, self.n) else: @@ -12515,8 +12851,7 @@ else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images') % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) self.selected = [] return False else: @@ -12661,10 +12996,12 @@ if obj in [self.menuitem14, self.menuitem26]: command = shutil.copy2 title = 'Copying...' + t_title = _('Copying...') self.filechooserdialog3.set_title(_('Choose a destination directory where to copy')) elif obj in [self.menuitem15, self.menuitem27]: command = shutil.move title = 'Moving...' + t_title = _('Moving...') self.filechooserdialog3.set_title(_('Choose a destination directory where to move')) if self.selection_control(): self.filechooserdialog3.show() @@ -12683,7 +13020,7 @@ obj.hide() while gtk.events_pending(): gtk.main_iteration(False) - dialog = gtk.Dialog(title, None, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) + dialog = gtk.Dialog(t_title, None, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) dialog.set_size_request(250,70) dialog.set_position(gtk.WIN_POS_CENTER) dialog.connect('response', dialog_response) @@ -12804,14 +13141,13 @@ for i in self.label[:self.n]: i.set_size_request(self.width, -1) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) self.window1.show_all() self.button5.hide() self.progressbar3.hide() else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3, _('There are %s images') % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) self.selected = [] dialog.hide() pbar.set_fraction(0) @@ -13041,12 +13377,10 @@ self.attach_images() self.make_thumbnails(self.width, self.height, self.n) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images') % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) if self.radiobutton7.get_active(): try: self.hbox1.destroy() @@ -13178,15 +13512,13 @@ for i in self.label[:self.n]: i.set_size_request(self.width, -1) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) self.window1.show_all() self.button5.hide() self.progressbar3.hide() else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images') % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) self.selected = [] self.window5.hide() self.progressbar2.set_fraction(0) @@ -13361,13 +13693,11 @@ self.make_table() self.attach_images() self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) self.make_thumbnails(self.width, self.height, self.n) else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images') % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) self.image28.clear() self.window2.set_sensitive(False) @@ -13405,8 +13735,7 @@ if Sqlite.get_keywords(img): Sqlite.remove_row(img) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.n -1 , self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.n -1 , 'mb':self.total_size / 1000000.0}) def overwrite_dialog2(self, outfile): self.label94.set_text(_('A file named "%s" \nalready exists! Overwrite it?') % outfile) @@ -13880,11 +14209,10 @@ self.attach_images() self.make_thumbnails(self.width, self.height, self.n) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %d images Total size %f MB') % (self.n, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)d images Total size %(mb).3f MB') % {'n':self.n, 'mb':self.total_size / 1000000.0}) else: self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3, _('There are %d images') % self.n) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are 0 images')) if self.radiobutton7.get_active(): try: self.hbox1.destroy() @@ -13935,6 +14263,7 @@ self.cornice.opaque_paint(Color('black'), Color(color.to_string()), 0) def attach_images(self, images=None): + self.statusbar_push(self.statusbar4, self.context_id4, "") if self.checkbutton360.get_active(): self.height = self.width self.load_frame() @@ -14063,8 +14392,7 @@ for item in list_[:self.i]: self.total_size += os.path.getsize(self.get_real_path(item)) self.statusbar_pop(self.statusbar3, self.context_id3) - self.statusbar_push(self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.i, self.total_size / 1000000.0)) + self.statusbar_push(self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.i, 'mb':self.total_size / 1000000.0}) break if self.stop_gallery_thread.isSet(): return True @@ -14079,14 +14407,26 @@ thumblist += [(infile, filename, preview, self.rotate, True)] pixbuf = self.load_image(infile) else: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) + if sys.platform.startswith('win') and self.width == 260: # workaround for win32 portable version + pixbuf = gtk.gdk.pixbuf_new_from_file(unistr(os_path_join(cache, preview))) + w, h = pixbuf.get_width(), pixbuf.get_height() + if h > w: + pixbuf = pixbuf.scale_simple(int(self.height * float(w)/h), self.height, gtk.gdk.INTERP_BILINEAR) + else: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) else: if preview in cache_list: if self.thumbnail_was_modified(preview, filename): thumblist += [(infile, filename, preview, self.rotate, True)] pixbuf = self.load_image(infile) else: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) + if sys.platform.startswith('win') and self.width == 260: # workaround for win32 portable version + pixbuf = gtk.gdk.pixbuf_new_from_file(unistr(os_path_join(cache, preview))) + w, h = pixbuf.get_width(), pixbuf.get_height() + if h > w: + pixbuf = pixbuf.scale_simple(int(self.height * float(w)/h), self.height, gtk.gdk.INTERP_BILINEAR) + else: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) else: pixbuf = self.load_image(infile) if self.checkbutton360.get_active() and self.checkbutton36.get_active(): @@ -14367,8 +14707,7 @@ for item in list_[:self.i]: self.total_size += os.path.getsize(self.get_real_path(item)) gobject.idle_add(self.statusbar_pop, self.statusbar3, self.context_id3) - gobject.idle_add(self.statusbar_push, self.statusbar3, self.context_id3,\ - _('There are %s images Total size %f MB') % (self.i, self.total_size / 1000000.0)) + gobject.idle_add(self.statusbar_push, self.statusbar3, self.context_id3, _('There are %(n)s images Total size %(mb).3f MB') % {'n':self.i, 'mb':self.total_size / 1000000.0}) break if self.stop_gallery_thread.isSet(): return True @@ -14387,14 +14726,26 @@ thumblist += [(infile, filename, preview, self.rotate, True)] pixbuf = self.load_image(infile) else: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) + if sys.platform.startswith('win') and self.width == 260: # workaround for windows portable version + pixbuf = gtk.gdk.pixbuf_new_from_file(unistr(os_path_join(cache, preview))) + w, h = pixbuf.get_width(), pixbuf.get_height() + if h > w: + pixbuf = pixbuf.scale_simple(int(self.height * float(w)/h), self.height, gtk.gdk.INTERP_BILINEAR) + else: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) else: if preview in cache_list: if self.thumbnail_was_modified(preview, filename): thumblist += [(infile, filename, preview, self.rotate, True)] pixbuf = self.load_image(infile) else: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) + if sys.platform.startswith('win') and self.width == 260: # workaround for windows portable version + pixbuf = gtk.gdk.pixbuf_new_from_file(unistr(os_path_join(cache, preview))) + w, h = pixbuf.get_width(), pixbuf.get_height() + if h > w: + pixbuf = pixbuf.scale_simple(int(self.height * float(w)/h), self.height, gtk.gdk.INTERP_BILINEAR) + else: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(unistr(os_path_join(cache, preview)), self.width, self.height) else: pixbuf = self.load_image(infile) if self.checkbutton360.get_active() and self.checkbutton36.get_active(): @@ -14476,7 +14827,7 @@ return True dirname = os.path.dirname(filename) list_ = ['-fast', '-j', '-n', '-CreateDate', '-DateTimeOriginal', '-ModifyDate', '-Model', '-Software', - '-CameraModelID', '-FileSize', '-ISO', '-ExposureTime', '-FNumber', filename] + '-CameraModelID', '-FileSize', '-ISO', '-ExposureTime', '-FNumber', '-ExifImageWidth', '-ExifImageLength', '-ExifImageHeight', '-ImageWidth','-ImageHeight', filename] output = self.exiftool.get_metadata('string', *list_) if output: outputlist = json.loads(utf8conv(output)) @@ -14508,6 +14859,10 @@ text += '\nExposureTime: %s' % string if item.has_key('FNumber'): text += '\nFNumber: %.1f' % item['FNumber'] + self.grep_image_size(json.loads(output)) + self.image_size = self.image_size.replace('px', '') + if self.image_size: + text += '\nFormat: ' + self.image_size info = '' if self.temp in self.list[i]: keywords = Sqlite.get_keywords(filename) @@ -14601,6 +14956,7 @@ text = _('Are you sure to delete\nall cached thumbnails?') dialog8 = self.messagedialog(_("Attention!"), text, None, gtk.BUTTONS_OK_CANCEL, self.dialog12, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, None, (285,180)) dialog8.set_keep_above(True) + dialog8.set_size_request(300,-1) response = dialog8.run() if response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: dialog8.destroy() @@ -14927,28 +15283,28 @@ def store_batch_queue(self): self.batch_list[0] = [[row[0], row[2], row[3], row[4],0] for row in self.batchmodel] self.batch_list[1] = self.batch_queue - f = open(os.path.join(self.ark, 'batch.db'), 'w') - cPickle.dump(self.batch_list, f) + f = open(os.path.join(self.ark, 'batch.db'), 'wb') + cPickle.dump(self.batch_list, f, protocol=1) f.close() def store_albums_db(self): - f = open(os.path.join(self.ark, 'albums.db'), 'w') - cPickle.dump(self.albums, f) + f = open(os.path.join(self.ark, 'albums.db'), 'wb') + cPickle.dump(self.albums, f, protocol=1) f.close() def store_templates_db(self): - f = open(os.path.join(self.ark, 'templates.db'), 'w') - cPickle.dump(self.templates, f) + f = open(os.path.join(self.ark, 'templates.db'), 'wb') + cPickle.dump(self.templates, f, protocol=1) f.close() def store_mtimes_db(self): - f = open(os.path.join(self.ark, 'thumbs_mtimes.db'), 'w') - cPickle.dump(self.mtimes, f) + f = open(os.path.join(self.ark, 'thumbs_mtimes.db'), 'wb') + cPickle.dump(self.mtimes, f, protocol=1) f.close() def store_settings_db(self): - f = open(os.path.join(self.ark, 'settings'), 'w') - cPickle.dump(self.settings, f) + f = open(os.path.join(self.ark, 'settings'), 'wb') + cPickle.dump(self.settings, f, protocol=1) f.close() def destroy(self, *widget): @@ -14956,7 +15312,7 @@ win.set_size_request(250,50) win.set_decorated(False) win.set_position(gtk.WIN_POS_CENTER) - win.add(gtk.Label("saving data...")) + win.add(gtk.Label(_("saving data..."))) win.show_all() while gtk.events_pending(): gtk.main_iteration(False) Binary files /tmp/7qQ4u395Wo/gtkrawgallery-0.9.8/locale/it/LC_MESSAGES/gtkrawgallery.mo and /tmp/ycqBWFhi1G/gtkrawgallery-0.9.9/locale/it/LC_MESSAGES/gtkrawgallery.mo differ diff -Nru gtkrawgallery-0.9.8/messages.pot gtkrawgallery-0.9.9/messages.pot --- gtkrawgallery-0.9.8/messages.pot 2012-02-19 16:51:32.000000000 +0000 +++ gtkrawgallery-0.9.9/messages.pot 2013-09-08 07:04:35.000000000 +0000 @@ -1,576 +1,2801 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-02-19 17:51+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#: gtkrawgallery.py:1099 gtkrawgallery.py:3370 -msgid "Disconnect GUI" -msgstr "" - -#: gtkrawgallery.py:1143 -msgid "Hide Sidebar" -msgstr "" - -#: gtkrawgallery.py:1153 -msgid "Enter one or more tags separated by comma or semicolon" -msgstr "" - -#: gtkrawgallery.py:1683 gtkrawgallery.py:7411 gtkrawgallery.py:7429 -#: gtkrawgallery.py:7447 -msgid "Updating Metadata..." -msgstr "" - -#: gtkrawgallery.py:2504 gtkrawgallery.py:2912 gtkrawgallery.py:3131 -#: gtkrawgallery.py:7187 gtkrawgallery.py:7230 gtkrawgallery.py:7437 -#: gtkrawgallery.py:11839 -msgid "Info!" -msgstr "" - -#: gtkrawgallery.py:2504 gtkrawgallery.py:2912 gtkrawgallery.py:3131 -#: gtkrawgallery.py:7187 gtkrawgallery.py:7437 gtkrawgallery.py:11839 -msgid "No file selected!" -msgstr "" - -#: gtkrawgallery.py:2596 -msgid "Cannot read profile!" -msgstr "" - -#: gtkrawgallery.py:2803 -msgid "Insert a name" -msgstr "" - -#: gtkrawgallery.py:2804 -msgid "Save WB Presets" -msgstr "" - -#: gtkrawgallery.py:2826 gtkrawgallery.py:3638 gtkrawgallery.py:4568 -#: gtkrawgallery.py:5471 gtkrawgallery.py:6833 gtkrawgallery.py:7132 -#: gtkrawgallery.py:7240 gtkrawgallery.py:7892 gtkrawgallery.py:8574 -#: gtkrawgallery.py:12371 gtkrawgallery.py:12503 gtkrawgallery.py:12579 -#: gtkrawgallery.py:13888 -msgid "Attention!" -msgstr "" - -#: gtkrawgallery.py:2826 -msgid "WB Name missing!" -msgstr "" - -#: gtkrawgallery.py:2886 gtkrawgallery.py:3113 -msgid "loading previews..." -msgstr "" - -#: gtkrawgallery.py:2907 gtkrawgallery.py:2927 gtkrawgallery.py:3129 -#: gtkrawgallery.py:7742 gtkrawgallery.py:7753 -#, python-format -msgid "Total size: %f Mb" -msgstr "" - -#: gtkrawgallery.py:2957 -msgid "No device found. Burn disabled!" -msgstr "" - -#: gtkrawgallery.py:2977 -msgid "Device not found!" -msgstr "" - -#: gtkrawgallery.py:2994 -msgid "creating disk image..." -msgstr "" - -#: gtkrawgallery.py:3026 -msgid "starting cdrecord..." -msgstr "" - -#: gtkrawgallery.py:3036 -msgid "Cannot load media!" -msgstr "" - -#: gtkrawgallery.py:3073 -msgid "" -"Cdrecord Error!\n" -"Read debugging output for more info!" -msgstr "" - -#: gtkrawgallery.py:3075 -msgid "Disk successfully burned!" -msgstr "" - -#: gtkrawgallery.py:3166 -msgid "Server missing!" -msgstr "" - -#: gtkrawgallery.py:3172 -msgid "User missing!" -msgstr "" - -#: gtkrawgallery.py:3180 -msgid "Password missing!" -msgstr "" - -#: gtkrawgallery.py:3190 -msgid "Sender address missing!" -msgstr "" - -#: gtkrawgallery.py:3195 -msgid "Destination address missing!" -msgstr "" - -#: gtkrawgallery.py:3200 -msgid "Subject missing!" -msgstr "" - -#: gtkrawgallery.py:3212 -msgid "connecting..." -msgstr "" - -#: gtkrawgallery.py:3221 -msgid "connection failed!" -msgstr "" - -#: gtkrawgallery.py:3241 -msgid "sending..." -msgstr "" - -#: gtkrawgallery.py:3264 -msgid "Mail successfully sent!" -msgstr "" - -#: gtkrawgallery.py:3273 gtkrawgallery.py:9771 -msgid "Error!" -msgstr "" - -#: gtkrawgallery.py:3301 -msgid "Processing Image..." -msgstr "" - -#: gtkrawgallery.py:3375 -msgid "Connect GUI" -msgstr "" - -#: gtkrawgallery.py:3638 gtkrawgallery.py:4568 -msgid "" -"No workflow to save.\n" -"Please, make a new one!" -msgstr "" - -#: gtkrawgallery.py:4561 -msgid "The workflow was saved for next conversion!" -msgstr "" - -#: gtkrawgallery.py:5471 -msgid "You must select a rectangle first!" -msgstr "" - -#: gtkrawgallery.py:5561 -msgid "Sharpening..." -msgstr "" - -#: gtkrawgallery.py:5598 -msgid "Unsharp Mask..." -msgstr "" - -#: gtkrawgallery.py:5636 -msgid "Blurring..." -msgstr "" - -#: gtkrawgallery.py:5694 -msgid "Charcoal..." -msgstr "" - -#: gtkrawgallery.py:5731 -msgid "Sketch..." -msgstr "" - -#: gtkrawgallery.py:5770 -msgid "Posterize..." -msgstr "" - -#: gtkrawgallery.py:5806 -msgid "Negate..." -msgstr "" - -#: gtkrawgallery.py:5842 -msgid "Noising..." -msgstr "" - -#: gtkrawgallery.py:5876 -msgid "Denoising..." -msgstr "" - -#: gtkrawgallery.py:5911 -msgid "Median Filter..." -msgstr "" - -#: gtkrawgallery.py:5946 -msgid "Sepia Tone..." -msgstr "" - -#: gtkrawgallery.py:6158 -msgid "Oil Paint..." -msgstr "" - -#: gtkrawgallery.py:6193 -msgid "Spread..." -msgstr "" - -#: gtkrawgallery.py:6228 -msgid "Despeckling..." -msgstr "" - -#: gtkrawgallery.py:6819 -msgid "The file was added to the batch queue!" -msgstr "" - -#: gtkrawgallery.py:6833 -msgid "Cannot add twin items!" -msgstr "" - -#: gtkrawgallery.py:6994 -#, python-format -msgid "Batch Manager - processing file %s - remaining %s" -msgstr "" - -#: gtkrawgallery.py:6998 -msgid "completed" -msgstr "" - -#: gtkrawgallery.py:7009 -msgid "Batch Manager - Queue completed!" -msgstr "" - -#: gtkrawgallery.py:7132 -msgid "" -"Add preferred tags to list first!\n" -"Double click the value row to edit." -msgstr "" - -#: gtkrawgallery.py:7141 -msgid "Template name" -msgstr "" - -#: gtkrawgallery.py:7142 -msgid "Save metadata template" -msgstr "" - -#: gtkrawgallery.py:7175 -msgid "Operation not permitted!" -msgstr "" - -#: gtkrawgallery.py:7230 -msgid "Metadata was written!" -msgstr "" - -#: gtkrawgallery.py:7239 -msgid "" -"Are you sure to clear\n" -"the tags database?" -msgstr "" - -#: gtkrawgallery.py:7399 -#, python-format -msgid "" -"Exiftool cannot write %s\n" -"%s" -msgstr "" - -#: gtkrawgallery.py:7892 -msgid "Restore default settings?" -msgstr "" - -#: gtkrawgallery.py:8026 gtkrawgallery.py:8038 gtkrawgallery.py:8049 -#, python-format -msgid "Unknown file: %s" -msgstr "" - -#: gtkrawgallery.py:8464 -#, python-format -msgid "Convert Selection (%d files)" -msgstr "" - -#: gtkrawgallery.py:8559 -#, python-format -msgid "Cannot convert %s" -msgstr "" - -#: gtkrawgallery.py:8573 -#, python-format -msgid "" -"A file named:\n" -"\"%s\" already exists.\n" -"Do you want to overwrite it?" -msgstr "" - -#: gtkrawgallery.py:9042 gtkrawgallery.py:9085 -msgid "Saving..." -msgstr "" - -#: gtkrawgallery.py:9140 gtkrawgallery.py:9189 gtkrawgallery.py:9238 -#: gtkrawgallery.py:9287 -#, python-format -msgid "Converting %s of %s files..." -msgstr "" - -#: gtkrawgallery.py:9636 -#, python-format -msgid "" -"Attention! The %s archive has been modified!\n" -"The album will be updated!" -msgstr "" - -#: gtkrawgallery.py:9637 gtkrawgallery.py:11785 gtkrawgallery.py:12011 -msgid "Warning!" -msgstr "" - -#: gtkrawgallery.py:9708 gtkrawgallery.py:11804 gtkrawgallery.py:12107 -#: gtkrawgallery.py:12344 gtkrawgallery.py:12481 gtkrawgallery.py:12664 -#: gtkrawgallery.py:12708 gtkrawgallery.py:13349 gtkrawgallery.py:13652 -#, python-format -msgid "There are %s images Total size %f MB" -msgstr "" - -#: gtkrawgallery.py:9716 gtkrawgallery.py:9805 gtkrawgallery.py:9854 -msgid "There are 0 images" -msgstr "" - -#: gtkrawgallery.py:9730 -msgid "Open Album" -msgstr "" - -#: gtkrawgallery.py:9731 -msgid "Choose the album to open" -msgstr "" - -#: gtkrawgallery.py:9752 -msgid "Insert the new album name" -msgstr "" - -#: gtkrawgallery.py:9753 -msgid "New Album" -msgstr "" - -#: gtkrawgallery.py:9771 -msgid "" -"This album name already exists.\n" -"Choose another name!" -msgstr "" - -#: gtkrawgallery.py:9793 -#, python-format -msgid "" -"Are you sure to delete\n" -" %s ?" -msgstr "" - -#: gtkrawgallery.py:9794 gtkrawgallery.py:9838 -msgid "Delete Album" -msgstr "" - -#: gtkrawgallery.py:9839 -msgid "Choose the album to delete!" -msgstr "" - -#: gtkrawgallery.py:9889 -#, python-format -msgid "" -"Insert a new name for\n" -" %s" -msgstr "" - -#: gtkrawgallery.py:11784 -#, python-format -msgid "" -"Attention! Unknown file: %s\n" -" The gallery will be updated!" -msgstr "" - -#: gtkrawgallery.py:11813 gtkrawgallery.py:12113 gtkrawgallery.py:12348 -#: gtkrawgallery.py:12488 gtkrawgallery.py:12669 -#, python-format -msgid "There are %s images" -msgstr "" - -#: gtkrawgallery.py:11847 -msgid "Saving to album..." -msgstr "" - -#: gtkrawgallery.py:11963 -msgid "Choose a destination directory where to copy" -msgstr "" - -#: gtkrawgallery.py:11967 -msgid "Choose a destination directory where to move" -msgstr "" - -#: gtkrawgallery.py:12009 -msgid "Moving album images is not permitted!" -msgstr "" - -#: gtkrawgallery.py:12034 gtkrawgallery.py:12041 gtkrawgallery.py:12048 -msgid "" -"Operation terminated with errors!\n" -"Open the Error Log for details." -msgstr "" - -#: gtkrawgallery.py:12371 -msgid "Are you sure to delete selected images?" -msgstr "" - -#: gtkrawgallery.py:12407 -msgid "Deleting..." -msgstr "" - -#: gtkrawgallery.py:12502 gtkrawgallery.py:12578 -#, python-format -msgid "Delete %s ?" -msgstr "" - -#: gtkrawgallery.py:12711 -#, python-format -msgid "" -"A file named \"%s\" \n" -"already exists! Overwrite it?" -msgstr "" - -#: gtkrawgallery.py:12889 -msgid "Calculating timestamp..." -msgstr "" - -#: gtkrawgallery.py:12917 gtkrawgallery.py:13042 -msgid "Scan" -msgstr "" - -#: gtkrawgallery.py:13010 -msgid "Scanning..." -msgstr "" - -#: gtkrawgallery.py:13011 -msgid "Stop" -msgstr "" - -#: gtkrawgallery.py:13036 -#, python-format -msgid "Found: %s" -msgstr "" - -#: gtkrawgallery.py:13040 -msgid "" -"This tool will try to find tagged images\n" -"into the filesystem updating the tags database." -msgstr "" - -#: gtkrawgallery.py:13165 -#, python-format -msgid "There are %d images Total size %f MB" -msgstr "" - -#: gtkrawgallery.py:13168 -#, python-format -msgid "There are %d images" -msgstr "" - -#: gtkrawgallery.py:13887 -msgid "" -"Are you sure to delete\n" -"all cached thumbnails?" -msgstr "" - -#: gtkrawgallery.py:13975 -msgid "Loading histogram..." -msgstr "" - -#: src/gtkrg_facebook.py:196 src/gtkrg_flickr.py:321 -#, python-format -msgid "Uploading %s ..." -msgstr "" - -#: src/gtkrg_facebook.py:331 src/gtkrg_flickr.py:418 -msgid "Retrieving albums..." -msgstr "" - -#: src/gtkrg_facebook.py:341 -msgid "Token expired!" -msgstr "" - -#: src/gtkrg_facebook.py:347 -#, python-format -msgid "Publish on %s Facebook account" -msgstr "" - -#: src/gtkrg_facebook.py:360 src/gtkrg_flickr.py:433 -#, python-format -msgid "you have %s albums" -msgstr "" - -#: src/gtkrg_facebook.py:386 src/gtkrg_flickr.py:445 -msgid "Authenticating..." -msgstr "" - -#: src/gtkrg_facebook.py:412 src/gtkrg_flickr.py:495 src/gtkrg_flickr.py:504 -#: src/gtkrg_flickr.py:507 -msgid "Log-in failed!" -msgstr "" - -#: src/gtkrg_facebook.py:459 -msgid "Publish on Facebook" -msgstr "" - -#: src/gtkrg_facebook.py:460 -msgid "Log into Facebook" -msgstr "" - -#: src/gtkrg_flickr.py:41 -msgid "Publish on Flickr" -msgstr "" - -#: src/gtkrg_flickr.py:42 -msgid "Log into Flickr" -msgstr "" - -#: src/gtkrg_flickr.py:271 -msgid "connection error!" -msgstr "" - -#: src/gtkrg_flickr.py:436 src/gtkrg_smtp.py:50 -msgid "Login failed!" -msgstr "" - -#: src/gtkrg_flickr.py:491 src/gtkrg_flickr.py:500 -#, python-format -msgid "Publish on %s Flickr account" -msgstr "" - -#: src/gtkrg_print.py:218 -#, python-format -msgid "%.1f x %.1f millimeters" -msgstr "" - -#: src/gtkrg_print.py:331 -#, python-format -msgid "%.3f x %.3f %s" -msgstr "" - -#: src/gtkrg_smtp.py:45 -msgid "Connection failed!" -msgstr "" - -#: src/gtkrg_smtp.py:58 -msgid "Disconnection failed!" -msgstr "" - -#: src/gtkrg_smtp.py:98 -msgid "Email has not been sent" -msgstr "" +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-09-08 09:04+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../gtkrawgallery.py:268 ../gtkrawgallery.py:4352 ../gtkrawgallery.py:5149 +msgid "Original" +msgstr "" + +#: ../gtkrawgallery.py:373 gtkrg.ui.h:24 +msgid "Albums" +msgstr "" + +#: ../gtkrawgallery.py:387 +msgid "Directory Tree" +msgstr "" + +#: ../gtkrawgallery.py:466 ../gtkrawgallery.py:480 gtkrg.ui.h:176 +msgid "Keywords" +msgstr "" + +#: ../gtkrawgallery.py:528 gtkrg.ui.h:403 +msgid "White" +msgstr "" + +#: ../gtkrawgallery.py:529 +msgid "Grey" +msgstr "" + +#: ../gtkrawgallery.py:530 gtkrg.ui.h:41 +msgid "Black" +msgstr "" + +#: ../gtkrawgallery.py:569 +msgid "Intersection" +msgstr "" + +#: ../gtkrawgallery.py:570 +msgid "Union" +msgstr "" + +#: ../gtkrawgallery.py:593 +msgid "Keep embedded profile as input profile" +msgstr "" + +#: ../gtkrawgallery.py:594 +msgid "Ignore embedded profile" +msgstr "" + +#: ../gtkrawgallery.py:595 +msgid "Ask always" +msgstr "" + +#: ../gtkrawgallery.py:604 ../gtkrawgallery.py:801 gtkrg.ui.h:354 +msgid "Tags" +msgstr "" + +#: ../gtkrawgallery.py:631 ../gtkrawgallery.py:736 +msgid "Preview" +msgstr "" + +#: ../gtkrawgallery.py:634 +msgid "Attachment" +msgstr "" + +#: ../gtkrawgallery.py:645 ../gtkrawgallery.py:662 ../gtkrawgallery.py:679 +#: ../gtkrawgallery.py:696 gtkrg.ui.h:127 +msgid "Favorites" +msgstr "" + +#: ../gtkrawgallery.py:732 +msgid "Enabled" +msgstr "" + +#: ../gtkrawgallery.py:755 +msgid "WB Name" +msgstr "" + +#: ../gtkrawgallery.py:762 +msgid "Red multiplier" +msgstr "" + +#: ../gtkrawgallery.py:769 +msgid "Green multiplier" +msgstr "" + +#: ../gtkrawgallery.py:776 +msgid "Blue multiplier" +msgstr "" + +#: ../gtkrawgallery.py:790 +msgid "workflow history" +msgstr "" + +#: ../gtkrawgallery.py:1145 +msgid "Sound files" +msgstr "" + +#: ../gtkrawgallery.py:1146 +msgid "All known images" +msgstr "" + +#: ../gtkrawgallery.py:1147 +msgid "ICC Profile" +msgstr "" + +#: ../gtkrawgallery.py:1148 +msgid "GDKPixbuf known images" +msgstr "" + +#: ../gtkrawgallery.py:1149 +msgid "Raw images" +msgstr "" + +#: ../gtkrawgallery.py:1183 ../gtkrawgallery.py:3455 +msgid "Disconnect GUI" +msgstr "" + +#: ../gtkrawgallery.py:1225 +msgid "Hide Sidebar" +msgstr "" + +#: ../gtkrawgallery.py:1235 +msgid "Enter one or more tags separated by comma or semicolon" +msgstr "" + +#: ../gtkrawgallery.py:2596 ../gtkrawgallery.py:8150 gtkrg.ui.h:159 +msgid "Info!" +msgstr "" + +#: ../gtkrawgallery.py:2596 +msgid "No file selected!" +msgstr "" + +#: ../gtkrawgallery.py:2693 +msgid "Cannot read profile!" +msgstr "" + +#: ../gtkrawgallery.py:2896 +msgid "Insert a name" +msgstr "" + +#: ../gtkrawgallery.py:2897 +msgid "Save WB Presets" +msgstr "" + +#: ../gtkrawgallery.py:2919 ../gtkrawgallery.py:3920 ../gtkrawgallery.py:4869 +#: ../gtkrawgallery.py:6264 ../gtkrawgallery.py:7755 ../gtkrawgallery.py:8060 +#: ../gtkrawgallery.py:8160 ../gtkrawgallery.py:8782 ../gtkrawgallery.py:8915 +#: ../gtkrawgallery.py:9577 ../gtkrawgallery.py:13406 +#: ../gtkrawgallery.py:13536 ../gtkrawgallery.py:13612 +#: ../gtkrawgallery.py:14957 gtkrg.ui.h:33 +msgid "Attention!" +msgstr "" + +#: ../gtkrawgallery.py:2919 +msgid "WB Name missing!" +msgstr "" + +#: ../gtkrawgallery.py:2979 ../gtkrawgallery.py:3216 +msgid "loading previews..." +msgstr "" + +#: ../gtkrawgallery.py:3000 ../gtkrawgallery.py:3015 ../gtkrawgallery.py:3232 +#: ../gtkrawgallery.py:8701 ../gtkrawgallery.py:8712 +#, python-format +msgid "Total size: %.3f Mb" +msgstr "" + +#: ../gtkrawgallery.py:3039 +msgid "" +"This panel permits to edit a custom metadata list.\n" +"1) select a group;\n" +"2) select a tag;\n" +"3) click \"add to list\";\n" +"Repeat the previous three points to add other tags;\n" +"4) Double click on the value column to edit;\n" +"5) Click the \"write metadata\" button.\n" +"You can save the list as template." +msgstr "" + +#: ../gtkrawgallery.py:3045 +msgid "About Advanced Metadata Editor" +msgstr "" + +#: ../gtkrawgallery.py:3060 +msgid "No device found. Burn disabled!" +msgstr "" + +#: ../gtkrawgallery.py:3080 +msgid "Device not found!" +msgstr "" + +#: ../gtkrawgallery.py:3097 +msgid "creating disk image..." +msgstr "" + +#: ../gtkrawgallery.py:3129 +msgid "starting cdrecord..." +msgstr "" + +#: ../gtkrawgallery.py:3139 +msgid "Cannot load media!" +msgstr "" + +#: ../gtkrawgallery.py:3176 +msgid "" +"Cdrecord Error!\n" +"Read debugging output for more info!" +msgstr "" + +#: ../gtkrawgallery.py:3178 +msgid "Disk successfully burned!" +msgstr "" + +#: ../gtkrawgallery.py:3264 +msgid "Server missing!" +msgstr "" + +#: ../gtkrawgallery.py:3270 +msgid "User missing!" +msgstr "" + +#: ../gtkrawgallery.py:3278 +msgid "Password missing!" +msgstr "" + +#: ../gtkrawgallery.py:3288 +msgid "Sender address missing!" +msgstr "" + +#: ../gtkrawgallery.py:3293 +msgid "Destination address missing!" +msgstr "" + +#: ../gtkrawgallery.py:3298 +msgid "Subject missing!" +msgstr "" + +#: ../gtkrawgallery.py:3310 +msgid "connecting..." +msgstr "" + +#: ../gtkrawgallery.py:3319 +msgid "connection failed!" +msgstr "" + +#: ../gtkrawgallery.py:3339 +msgid "sending..." +msgstr "" + +#: ../gtkrawgallery.py:3345 +msgid "Mail successfully sent!" +msgstr "" + +#: ../gtkrawgallery.py:3355 ../gtkrawgallery.py:10806 +msgid "Error!" +msgstr "" + +#: ../gtkrawgallery.py:3383 +msgid "Processing Image..." +msgstr "" + +#: ../gtkrawgallery.py:3443 +msgid "" +"A photo manager\n" +"for digital camera raw image development." +msgstr "" + +#: ../gtkrawgallery.py:3447 +msgid "Visit the Project Page" +msgstr "" + +#: ../gtkrawgallery.py:3460 +msgid "Connect GUI" +msgstr "" + +#: ../gtkrawgallery.py:3595 +msgid "save raw style" +msgstr "" + +#: ../gtkrawgallery.py:3597 ../gtkrawgallery.py:5065 +msgid "Insert a style name" +msgstr "" + +#: ../gtkrawgallery.py:3623 +#, python-format +msgid "Save %s changes?" +msgstr "" + +#: ../gtkrawgallery.py:3624 +msgid "Save raw style" +msgstr "" + +#: ../gtkrawgallery.py:3649 ../gtkrawgallery.py:7285 ../gtkrawgallery.py:10828 +#, python-format +msgid "" +"Are you sure to delete\n" +" %s ?" +msgstr "" + +#: ../gtkrawgallery.py:3650 +msgid "Delete raw style" +msgstr "" + +#: ../gtkrawgallery.py:3720 ../gtkrawgallery.py:8341 ../gtkrawgallery.py:8362 +#: ../gtkrawgallery.py:8378 ../gtkrawgallery.py:8401 ../gtkrawgallery.py:8795 +#: ../gtkrawgallery.py:8829 +msgid "Updating Metadata..." +msgstr "" + +#: ../gtkrawgallery.py:3729 ../gtkrawgallery.py:12883 +msgid "Saving to album..." +msgstr "" + +#: ../gtkrawgallery.py:3920 ../gtkrawgallery.py:4869 +msgid "" +"No workflow to save.\n" +"Please, make a new one!" +msgstr "" + +#: ../gtkrawgallery.py:4578 +msgid "Channel mixer" +msgstr "" + +#: ../gtkrawgallery.py:4756 +msgid "Levels" +msgstr "" + +#: ../gtkrawgallery.py:4772 +msgid "Brightness and saturation" +msgstr "" + +#: ../gtkrawgallery.py:4792 +msgid "Color balance" +msgstr "" + +#: ../gtkrawgallery.py:4808 gtkrg.ui.h:79 +msgid "Contrast" +msgstr "" + +#: ../gtkrawgallery.py:4825 +msgid "Sigmoidal contrast" +msgstr "" + +#: ../gtkrawgallery.py:4952 ../gtkrawgallery.py:7362 +msgid "highlights" +msgstr "" + +#: ../gtkrawgallery.py:4954 ../gtkrawgallery.py:7364 +msgid "shadows" +msgstr "" + +#: ../gtkrawgallery.py:5063 gtkrg.ui.h:462 +msgid "save style" +msgstr "" + +#: ../gtkrawgallery.py:5149 +msgid "Style " +msgstr "" + +#: ../gtkrawgallery.py:5166 +#, python-format +msgid "" +"Are you sure to delete the style\n" +" %s ?" +msgstr "" + +#: ../gtkrawgallery.py:5167 +msgid "Delete style" +msgstr "" + +#: ../gtkrawgallery.py:6161 gtkrg.ui.h:313 +msgid "Scale" +msgstr "" + +#: ../gtkrawgallery.py:6181 +msgid "Flip" +msgstr "" + +#: ../gtkrawgallery.py:6185 +msgid "Flop" +msgstr "" + +#: ../gtkrawgallery.py:6189 +msgid "Rotate 90" +msgstr "" + +#: ../gtkrawgallery.py:6193 +msgid "Rotate -90" +msgstr "" + +#: ../gtkrawgallery.py:6230 gtkrg.ui.h:46 +msgid "Border" +msgstr "" + +#: ../gtkrawgallery.py:6258 +msgid "Fine rotation" +msgstr "" + +#: ../gtkrawgallery.py:6264 +msgid "You must select a rectangle first!" +msgstr "" + +#: ../gtkrawgallery.py:6314 gtkrg.ui.h:82 +msgid "Crop" +msgstr "" + +#: ../gtkrawgallery.py:6359 +msgid "Sharpening..." +msgstr "" + +#: ../gtkrawgallery.py:6390 gtkrg.ui.h:328 +msgid "Sharpen" +msgstr "" + +#: ../gtkrawgallery.py:6398 +msgid "Unsharp Mask..." +msgstr "" + +#: ../gtkrawgallery.py:6430 +msgid "Unsharp mask" +msgstr "" + +#: ../gtkrawgallery.py:6438 +msgid "Blurring..." +msgstr "" + +#: ../gtkrawgallery.py:6490 gtkrg.ui.h:45 +msgid "Blur" +msgstr "" + +#: ../gtkrawgallery.py:6498 +msgid "Charcoal..." +msgstr "" + +#: ../gtkrawgallery.py:6527 gtkrg.ui.h:57 +msgid "Charcoal" +msgstr "" + +#: ../gtkrawgallery.py:6537 +msgid "Sketch..." +msgstr "" + +#: ../gtkrawgallery.py:6567 gtkrg.ui.h:337 +msgid "Sketch" +msgstr "" + +#: ../gtkrawgallery.py:6578 +msgid "Posterize..." +msgstr "" + +#: ../gtkrawgallery.py:6606 gtkrg.ui.h:267 +msgid "Posterize" +msgstr "" + +#: ../gtkrawgallery.py:6616 +msgid "Negate..." +msgstr "" + +#: ../gtkrawgallery.py:6644 gtkrg.ui.h:207 +msgid "Negate" +msgstr "" + +#: ../gtkrawgallery.py:6654 +msgid "Noising..." +msgstr "" + +#: ../gtkrawgallery.py:6682 +msgid "Add noise" +msgstr "" + +#: ../gtkrawgallery.py:6690 +msgid "Denoising..." +msgstr "" + +#: ../gtkrawgallery.py:6719 +msgid "Reduce noise" +msgstr "" + +#: ../gtkrawgallery.py:6727 +msgid "Median Filter..." +msgstr "" + +#: ../gtkrawgallery.py:6756 +msgid "Median filter" +msgstr "" + +#: ../gtkrawgallery.py:6764 +msgid "Sepia Tone..." +msgstr "" + +#: ../gtkrawgallery.py:6793 +msgid "Sepia tone" +msgstr "" + +#: ../gtkrawgallery.py:6820 +msgid "Grayscale" +msgstr "" + +#: ../gtkrawgallery.py:6853 +msgid "Vignette removal" +msgstr "" + +#: ../gtkrawgallery.py:6864 gtkrg.ui.h:247 +msgid "Normalize" +msgstr "" + +#: ../gtkrawgallery.py:6895 +msgid "Distort" +msgstr "" + +#: ../gtkrawgallery.py:6977 +msgid "Red eye removal" +msgstr "" + +#: ../gtkrawgallery.py:6988 +msgid "Oil Paint..." +msgstr "" + +#: ../gtkrawgallery.py:7017 +msgid "Oil paint" +msgstr "" + +#: ../gtkrawgallery.py:7025 +msgid "Spread..." +msgstr "" + +#: ../gtkrawgallery.py:7054 gtkrg.ui.h:341 +msgid "Spread" +msgstr "" + +#: ../gtkrawgallery.py:7062 +msgid "Despeckling..." +msgstr "" + +#: ../gtkrawgallery.py:7088 +msgid "Despeckle" +msgstr "" + +#: ../gtkrawgallery.py:7245 +msgid "Curve" +msgstr "" + +#: ../gtkrawgallery.py:7257 +msgid "save curve" +msgstr "" + +#: ../gtkrawgallery.py:7259 gtkrg.ui.h:162 +msgid "Insert a curve name" +msgstr "" + +#: ../gtkrawgallery.py:7286 +msgid "Delete curve preset" +msgstr "" + +#: ../gtkrawgallery.py:7480 gtkrg.ui.h:385 +msgid "Tint" +msgstr "" + +#: ../gtkrawgallery.py:7624 +msgid "White balance" +msgstr "" + +#: ../gtkrawgallery.py:7654 +msgid "Color temperature" +msgstr "" + +#: ../gtkrawgallery.py:7741 +msgid "The file was added to the batch queue!" +msgstr "" + +#: ../gtkrawgallery.py:7755 +msgid "Cannot add twin items!" +msgstr "" + +#: ../gtkrawgallery.py:7921 +#, python-format +msgid "Batch Manager - processing file %(run)s - remaining %(rem)s" +msgstr "" + +#: ../gtkrawgallery.py:7925 +msgid "completed" +msgstr "" + +#: ../gtkrawgallery.py:7936 +msgid "Batch Manager - Queue completed!" +msgstr "" + +#: ../gtkrawgallery.py:8060 +msgid "" +"Add preferred tags to list first!\n" +"Double click the value row to edit." +msgstr "" + +#: ../gtkrawgallery.py:8069 +msgid "Template name" +msgstr "" + +#: ../gtkrawgallery.py:8070 gtkrg.ui.h:312 +msgid "Save metadata template" +msgstr "" + +#: ../gtkrawgallery.py:8103 +msgid "Operation not permitted!" +msgstr "" + +#: ../gtkrawgallery.py:8150 +msgid "Metadata was written!" +msgstr "" + +#: ../gtkrawgallery.py:8159 +msgid "" +"Are you sure to clear\n" +"the tags database?" +msgstr "" + +#: ../gtkrawgallery.py:8311 +#, python-format +msgid "" +"Exiftool cannot write %(name_)s\n" +"%(output_)s" +msgstr "" + +#: ../gtkrawgallery.py:8781 +#, python-format +msgid "Remove tag \"%s\" ?" +msgstr "" + +#: ../gtkrawgallery.py:8818 +msgid "New tag name" +msgstr "" + +#: ../gtkrawgallery.py:8819 gtkrg.ui.h:291 +msgid "Rename tag" +msgstr "" + +#: ../gtkrawgallery.py:8915 +msgid "Restore default settings?" +msgstr "" + +#: ../gtkrawgallery.py:9025 ../gtkrawgallery.py:9037 ../gtkrawgallery.py:9048 +#, python-format +msgid "Unknown file: %s" +msgstr "" + +#: ../gtkrawgallery.py:9110 +msgid "Embedded Profile" +msgstr "" + +#: ../gtkrawgallery.py:9467 +#, python-format +msgid "Convert Selection (%d files)" +msgstr "" + +#: ../gtkrawgallery.py:9562 +#, python-format +msgid "Cannot convert %s" +msgstr "" + +#: ../gtkrawgallery.py:9576 +#, python-format +msgid "" +"A file named:\n" +"\"%s\" already exists.\n" +"Do you want to overwrite it?" +msgstr "" + +#: ../gtkrawgallery.py:10072 ../gtkrawgallery.py:10115 +msgid "Saving..." +msgstr "" + +#: ../gtkrawgallery.py:10170 ../gtkrawgallery.py:10219 +#: ../gtkrawgallery.py:10268 ../gtkrawgallery.py:10317 +#, python-format +msgid "Converting %(n)s of %(sel)s files..." +msgstr "" + +#: ../gtkrawgallery.py:10401 +#, python-format +msgid "% of " +msgstr "" + +#: ../gtkrawgallery.py:10463 ../gtkrawgallery.py:10576 +#: ../gtkrawgallery.py:10744 ../gtkrawgallery.py:12846 +#: ../gtkrawgallery.py:13144 ../gtkrawgallery.py:13380 +#: ../gtkrawgallery.py:13515 ../gtkrawgallery.py:13696 +#: ../gtkrawgallery.py:13738 ../gtkrawgallery.py:14395 +#: ../gtkrawgallery.py:14710 +#, python-format +msgid "There are %(n)s images Total size %(mb).3f MB" +msgstr "" + +#: ../gtkrawgallery.py:10466 ../gtkrawgallery.py:10579 +#: ../gtkrawgallery.py:10752 ../gtkrawgallery.py:10840 +#: ../gtkrawgallery.py:10889 ../gtkrawgallery.py:12854 +#: ../gtkrawgallery.py:13150 ../gtkrawgallery.py:13383 +#: ../gtkrawgallery.py:13521 ../gtkrawgallery.py:13700 +#: ../gtkrawgallery.py:14215 +msgid "There are 0 images" +msgstr "" + +#: ../gtkrawgallery.py:10611 +#, python-format +msgid "There are %(n)s images Total size %(mb).3f MB " +msgstr "" + +#: ../gtkrawgallery.py:10614 +#, python-format +msgid "There are %s images" +msgstr "" + +#: ../gtkrawgallery.py:10657 +#, python-format +msgid "" +"Attention! The %s archive has been modified!\n" +"The album will be updated!" +msgstr "" + +#: ../gtkrawgallery.py:10658 ../gtkrawgallery.py:12828 +#: ../gtkrawgallery.py:13049 +msgid "Warning!" +msgstr "" + +#: ../gtkrawgallery.py:10765 gtkrg.ui.h:249 +msgid "Open Album" +msgstr "" + +#: ../gtkrawgallery.py:10766 +msgid "Choose the album to open" +msgstr "" + +#: ../gtkrawgallery.py:10787 gtkrg.ui.h:168 publisher.ui.h:10 +msgid "Insert the new album name" +msgstr "" + +#: ../gtkrawgallery.py:10788 gtkrg.ui.h:209 publisher.ui.h:13 +msgid "New Album" +msgstr "" + +#: ../gtkrawgallery.py:10806 +msgid "" +"This album name already exists.\n" +"Choose another name!" +msgstr "" + +#: ../gtkrawgallery.py:10829 ../gtkrawgallery.py:10873 gtkrg.ui.h:94 +msgid "Delete Album" +msgstr "" + +#: ../gtkrawgallery.py:10874 +msgid "Choose the album to delete!" +msgstr "" + +#: ../gtkrawgallery.py:10924 +#, python-format +msgid "" +"Insert a new name for\n" +" %s" +msgstr "" + +#: ../gtkrawgallery.py:12827 +#, python-format +msgid "" +"Attention! Unknown file: %s\n" +" The gallery will be updated!" +msgstr "" + +#: ../gtkrawgallery.py:12999 +msgid "Copying..." +msgstr "" + +#: ../gtkrawgallery.py:13000 +msgid "Choose a destination directory where to copy" +msgstr "" + +#: ../gtkrawgallery.py:13004 +msgid "Moving..." +msgstr "" + +#: ../gtkrawgallery.py:13005 +msgid "Choose a destination directory where to move" +msgstr "" + +#: ../gtkrawgallery.py:13047 +msgid "Moving album images is not permitted!" +msgstr "" + +#: ../gtkrawgallery.py:13072 ../gtkrawgallery.py:13079 +#: ../gtkrawgallery.py:13086 +msgid "" +"Operation terminated with errors!\n" +"Open the Error Log for details." +msgstr "" + +#: ../gtkrawgallery.py:13406 +msgid "Are you sure to delete selected images?" +msgstr "" + +#: ../gtkrawgallery.py:13442 +msgid "Deleting..." +msgstr "" + +#: ../gtkrawgallery.py:13535 ../gtkrawgallery.py:13611 +#, python-format +msgid "Delete %s ?" +msgstr "" + +#: ../gtkrawgallery.py:13741 +#, python-format +msgid "" +"A file named \"%s\" \n" +"already exists! Overwrite it?" +msgstr "" + +#: ../gtkrawgallery.py:13929 +msgid "Calculating timestamp..." +msgstr "" + +#: ../gtkrawgallery.py:13958 ../gtkrawgallery.py:14080 gtkrg.ui.h:314 +msgid "Scan" +msgstr "" + +#: ../gtkrawgallery.py:14048 +msgid "Scanning..." +msgstr "" + +#: ../gtkrawgallery.py:14049 +msgid "Stop" +msgstr "" + +#: ../gtkrawgallery.py:14074 +#, python-format +msgid "Found: %s" +msgstr "" + +#: ../gtkrawgallery.py:14078 gtkrg.ui.h:381 +msgid "" +"This tool will try to find tagged images\n" +"into the filesystem updating the tags database." +msgstr "" + +#: ../gtkrawgallery.py:14212 +#, python-format +msgid "There are %(n)d images Total size %(mb).3f MB" +msgstr "" + +#: ../gtkrawgallery.py:14956 +msgid "" +"Are you sure to delete\n" +"all cached thumbnails?" +msgstr "" + +#: ../gtkrawgallery.py:15045 +msgid "Loading histogram..." +msgstr "" + +#: ../gtkrawgallery.py:15315 +msgid "saving data..." +msgstr "" + +#: gtkrg_dropbox.py:42 +msgid "Upload into DropBox" +msgstr "" + +#: gtkrg_dropbox.py:57 +msgid "Dropbox user:" +msgstr "" + +#: gtkrg_dropbox.py:58 +msgid "New folder" +msgstr "" + +#: gtkrg_dropbox.py:59 +msgid "creates a new destination folder" +msgstr "" + +#: gtkrg_dropbox.py:60 +msgid "insert the folder name" +msgstr "" + +#: gtkrg_dropbox.py:62 +msgid "Upload to:" +msgstr "" + +#: gtkrg_dropbox.py:77 +msgid "Log into Dropbox" +msgstr "" + +#: gtkrg_dropbox.py:108 gtkrg_facebook.py:99 gtkrg_flickr.py:95 +msgid "
Page Loading Error!
" +msgstr "" + +#: gtkrg_dropbox.py:211 +msgid "not logged!" +msgstr "" + +#: gtkrg_dropbox.py:224 gtkrg_facebook.py:263 gtkrg_flickr.py:375 +#: gtkrg_picasaweb.py:287 +#, python-format +msgid "Uploading %s ..." +msgstr "" + +#: gtkrg_dropbox.py:287 gtkrg_facebook.py:391 gtkrg_facebook.py:488 +#: gtkrg_flickr.py:487 +msgid "Continue" +msgstr "" + +#: gtkrg_dropbox.py:290 +msgid "" +"
Log into Dropbox to authorize GTKRawGallery and click OK.

Click " +"Continue.

" +msgstr "" + +#: gtkrg_dropbox.py:302 gtkrg_dropbox.py:306 gtkrg_dropbox.py:361 +#: gtkrg_flickr.py:517 gtkrg_flickr.py:525 gtkrg_flickr.py:561 +#: gtkrg_picasaweb.py:418 +msgid "Log-in failed!" +msgstr "" + +#: gtkrg_dropbox.py:319 +msgid "authenticating..." +msgstr "" + +#: gtkrg_dropbox.py:334 +msgid "Retrieving directories..." +msgstr "" + +#: gtkrg_dropbox.py:344 +#, python-format +msgid "Upload into the DropBox of %s" +msgstr "" + +#: gtkrg_dropbox.py:345 +msgid "You are logged!" +msgstr "" + +#: gtkrg_facebook.py:75 +msgid "Log into Facebook" +msgstr "" + +#: gtkrg_facebook.py:408 gtkrg_flickr.py:458 gtkrg_picasaweb.py:348 +msgid "Retrieving albums..." +msgstr "" + +#: gtkrg_facebook.py:419 +msgid "Token expired!" +msgstr "" + +#: gtkrg_facebook.py:428 +#, python-format +msgid "Publish on %s Facebook account" +msgstr "" + +#: gtkrg_facebook.py:452 gtkrg_flickr.py:473 gtkrg_picasaweb.py:358 +#, python-format +msgid "you have %s albums" +msgstr "" + +#: gtkrg_facebook.py:469 +msgid "" +"
Log into your Facebook to authorize GTKRawGallery and click OK.

Click " +"Continue.

" +msgstr "" + +#: gtkrg_facebook.py:484 +msgid "Publish on Facebook" +msgstr "" + +#: gtkrg_flickr.py:47 +msgid "Publish on Flickr" +msgstr "" + +#: gtkrg_flickr.py:58 +msgid "Flickr user:" +msgstr "" + +#: gtkrg_flickr.py:68 +msgid "Log into Flickr" +msgstr "" + +#: gtkrg_flickr.py:325 +msgid "connection error!" +msgstr "" + +#: gtkrg_flickr.py:476 gtkrg_flickr.py:480 gtkrg_smtp.py:86 +msgid "Login failed!" +msgstr "" + +#: gtkrg_flickr.py:488 +msgid "" +"
Log into Flickr to authorize GTKRawGallery and click OK.

Click on " +"continue.

" +msgstr "" + +#: gtkrg_flickr.py:510 gtkrg_flickr.py:541 +#, python-format +msgid "Publish on %s Flickr account" +msgstr "" + +#: gtkrg_picasaweb.py:48 +msgid "Publish on Picasa Web Albums" +msgstr "" + +#: gtkrg_picasaweb.py:49 +msgid "Log into Picasa Web Albums" +msgstr "" + +#: gtkrg_picasaweb.py:386 +msgid "Authenticating..." +msgstr "" + +#: gtkrg_picasaweb.py:412 +msgid "Invalid username or password!" +msgstr "" + +#: gtkrg_print.py:222 +#, python-format +msgid "%(w).1f x %(h).1f millimeters" +msgstr "" + +#: gtkrg_print.py:334 +#, python-format +msgid "%(w).3f x %(h).3f %(text)s" +msgstr "" + +#: gtkrg_smtp.py:81 +msgid "Connection failed!" +msgstr "" + +#: gtkrg_smtp.py:93 +msgid "Disconnection failed!" +msgstr "" + +#: gtkrg_smtp.py:133 +msgid "Email has not been sent" +msgstr "" + +#: gtkrg.ui.h:1 +msgid "16 bit" +msgstr "" + +#: gtkrg.ui.h:2 +msgid "16 bit linear" +msgstr "" + +#: gtkrg.ui.h:3 +msgid "25" +msgstr "" + +#: gtkrg.ui.h:4 +msgid "8 bit" +msgstr "" + +#: gtkrg.ui.h:5 +msgid ":" +msgstr "" + +#: gtkrg.ui.h:6 +msgid "" +"Zoom in and right click on the red area!\n" +"Use the mouse wheel to adjust the circle size." +msgstr "" + +#: gtkrg.ui.h:8 +msgid "About Tag Manager" +msgstr "" + +#: gtkrg.ui.h:9 +msgid "Account Settings" +msgstr "" + +#: gtkrg.ui.h:10 +msgid "Add Border" +msgstr "" + +#: gtkrg.ui.h:11 +msgid "Add Fading Transition" +msgstr "" + +#: gtkrg.ui.h:12 +msgid "Add Noise" +msgstr "" + +#: gtkrg.ui.h:13 +msgid "Add To Batch Queue" +msgstr "" + +#: gtkrg.ui.h:14 +msgid "Add and click row to edit" +msgstr "" + +#: gtkrg.ui.h:15 +msgid "Add new keyword to list" +msgstr "" + +#: gtkrg.ui.h:16 +msgid "Add new tag to list" +msgstr "" + +#: gtkrg.ui.h:17 +msgid "Add tag to list" +msgstr "" + +#: gtkrg.ui.h:18 +msgid "Add to Album" +msgstr "" + +#: gtkrg.ui.h:19 +msgid "Add to album" +msgstr "" + +#: gtkrg.ui.h:20 +msgid "Add to list" +msgstr "" + +#: gtkrg.ui.h:21 +msgid "Advanced Metadata Editor" +msgstr "" + +#: gtkrg.ui.h:22 +msgid "Advanced controls" +msgstr "" + +#: gtkrg.ui.h:23 +msgid "Album" +msgstr "" + +#: gtkrg.ui.h:25 +msgid "Appearance" +msgstr "" + +#: gtkrg.ui.h:26 +msgid "Apply Tags" +msgstr "" + +#: gtkrg.ui.h:27 +msgid "Apply camera ICC profile" +msgstr "" + +#: gtkrg.ui.h:28 +msgid "Apply output ICC profile" +msgstr "" + +#: gtkrg.ui.h:29 +msgid "Apply style" +msgstr "" + +#: gtkrg.ui.h:30 +msgid "Apply tags" +msgstr "" + +#: gtkrg.ui.h:31 +msgid "Apply tags to selected images" +msgstr "" + +#: gtkrg.ui.h:32 +msgid "Attention" +msgstr "" + +#: gtkrg.ui.h:34 +msgid "" +"Attention, this image has an embedded ICC profile!\n" +"What do you want to do with it?" +msgstr "" + +#: gtkrg.ui.h:36 +msgid "Back" +msgstr "" + +#: gtkrg.ui.h:37 +msgid "Background Music" +msgstr "" + +#: gtkrg.ui.h:38 +msgid "Barrel" +msgstr "" + +#: gtkrg.ui.h:39 +msgid "Batch Manager" +msgstr "" + +#: gtkrg.ui.h:40 +msgid "Before" +msgstr "" + +#: gtkrg.ui.h:42 +msgid "Black point" +msgstr "" + +#: gtkrg.ui.h:43 +msgid "Blank disk first to write" +msgstr "" + +#: gtkrg.ui.h:44 +msgid "Blue" +msgstr "" + +#: gtkrg.ui.h:47 +msgid "Brightness:" +msgstr "" + +#: gtkrg.ui.h:48 +msgid "Browser" +msgstr "" + +#: gtkrg.ui.h:49 +msgid "Burn" +msgstr "" + +#: gtkrg.ui.h:50 +msgid "Burn disk" +msgstr "" + +#: gtkrg.ui.h:51 +msgid "Cache Thumbnails" +msgstr "" + +#: gtkrg.ui.h:52 +msgid "Cache level:" +msgstr "" + +#: gtkrg.ui.h:53 publisher.ui.h:5 +msgid "Cancel" +msgstr "" + +#: gtkrg.ui.h:54 +msgid "Channel Mixer" +msgstr "" + +#: gtkrg.ui.h:55 +msgid "Channel multipliers" +msgstr "" + +#: gtkrg.ui.h:56 +msgid "Channel:" +msgstr "" + +#: gtkrg.ui.h:58 +msgid "Check a category to tag image and vice versa" +msgstr "" + +#: gtkrg.ui.h:59 +msgid "Choose Presets to delete" +msgstr "" + +#: gtkrg.ui.h:60 +msgid "Choose a destination directory" +msgstr "" + +#: gtkrg.ui.h:61 +msgid "Choose a gallery layout:" +msgstr "" + +#: gtkrg.ui.h:62 +msgid "Choose album directory" +msgstr "" + +#: gtkrg.ui.h:63 +msgid "Choose destination album" +msgstr "" + +#: gtkrg.ui.h:64 +msgid "Choose the album name to delete" +msgstr "" + +#: gtkrg.ui.h:65 +msgid "Choose the template to delete" +msgstr "" + +#: gtkrg.ui.h:66 +msgid "Clear Cache" +msgstr "" + +#: gtkrg.ui.h:67 +msgid "Clear Keywords" +msgstr "" + +#: gtkrg.ui.h:68 +msgid "Clear Tag database" +msgstr "" + +#: gtkrg.ui.h:69 +msgid "Clear all tags" +msgstr "" + +#: gtkrg.ui.h:70 +msgid "Clear list when done!" +msgstr "" + +#: gtkrg.ui.h:71 +msgid "Clear tag list" +msgstr "" + +#: gtkrg.ui.h:72 +msgid "Close Development Window" +msgstr "" + +#: gtkrg.ui.h:73 +msgid "Color" +msgstr "" + +#: gtkrg.ui.h:74 +msgid "Color Balance" +msgstr "" + +#: gtkrg.ui.h:75 +msgid "Color Depth" +msgstr "" + +#: gtkrg.ui.h:76 +msgid "Color Management" +msgstr "" + +#: gtkrg.ui.h:77 +msgid "Color Manager" +msgstr "" + +#: gtkrg.ui.h:78 +msgid "Command:" +msgstr "" + +#: gtkrg.ui.h:80 +msgid "Copy to" +msgstr "" + +#: gtkrg.ui.h:81 +msgid "Correct chromatic aberration" +msgstr "" + +#: gtkrg.ui.h:83 +msgid "Curves" +msgstr "" + +#: gtkrg.ui.h:84 +msgid "Cyan" +msgstr "" + +#: gtkrg.ui.h:85 +msgid "Dark theme" +msgstr "" + +#: gtkrg.ui.h:86 +msgid "Dcraw" +msgstr "" + +#: gtkrg.ui.h:87 +msgid "Dcraw supported formats" +msgstr "" + +#: gtkrg.ui.h:88 +msgid "Debug" +msgstr "" + +#: gtkrg.ui.h:89 +msgid "Debugging output" +msgstr "" + +#: gtkrg.ui.h:90 +msgid "Default Settings" +msgstr "" + +#: gtkrg.ui.h:91 +msgid "Default theme" +msgstr "" + +#: gtkrg.ui.h:92 +msgid "Degree" +msgstr "" + +#: gtkrg.ui.h:93 +msgid "Delay:" +msgstr "" + +#: gtkrg.ui.h:95 +msgid "Delete Image" +msgstr "" + +#: gtkrg.ui.h:96 +msgid "Delete Presets" +msgstr "" + +#: gtkrg.ui.h:97 +msgid "Delete Template" +msgstr "" + +#: gtkrg.ui.h:98 +msgid "Delete current preset" +msgstr "" + +#: gtkrg.ui.h:99 +msgid "Denoise:" +msgstr "" + +#: gtkrg.ui.h:100 +msgid "Destination" +msgstr "" + +#: gtkrg.ui.h:101 +msgid "Destination:" +msgstr "" + +#: gtkrg.ui.h:102 +msgid "Details" +msgstr "" + +#: gtkrg.ui.h:103 +msgid "Device:" +msgstr "" + +#: gtkrg.ui.h:104 +msgid "Display ICC Profile:" +msgstr "" + +#: gtkrg.ui.h:105 +msgid "Display Rendering Intent" +msgstr "" + +#: gtkrg.ui.h:106 +msgid "Display profile Info!" +msgstr "" + +#: gtkrg.ui.h:107 +msgid "Do not ask me again" +msgstr "" + +#: gtkrg.ui.h:108 +msgid "Do you want to save last changes?" +msgstr "" + +#: gtkrg.ui.h:109 +msgid "Document mode (totally raw)" +msgstr "" + +#: gtkrg.ui.h:110 +msgid "Don't alert me again" +msgstr "" + +#: gtkrg.ui.h:111 +msgid "Don't alert me again!" +msgstr "" + +#: gtkrg.ui.h:112 +msgid "Don't ask me again" +msgstr "" + +#: gtkrg.ui.h:113 +msgid "Don't stretch or rotate raw pixels" +msgstr "" + +#: gtkrg.ui.h:114 +msgid "Edit WB Presets" +msgstr "" + +#: gtkrg.ui.h:115 +msgid "Effects" +msgstr "" + +#: gtkrg.ui.h:116 +msgid "Enable Color Management" +msgstr "" + +#: gtkrg.ui.h:117 +msgid "Enhance" +msgstr "" + +#: gtkrg.ui.h:118 +msgid "Error Log" +msgstr "" + +#: gtkrg.ui.h:119 +msgid "Exif" +msgstr "" + +#: gtkrg.ui.h:120 +msgid "Exiftool warning!" +msgstr "" + +#: gtkrg.ui.h:121 +msgid "Exposure" +msgstr "" + +#: gtkrg.ui.h:122 +msgid "Exposure, saturation, hue" +msgstr "" + +#: gtkrg.ui.h:123 +msgid "Extension" +msgstr "" + +#: gtkrg.ui.h:124 +msgid "Extension:" +msgstr "" + +#: gtkrg.ui.h:125 +msgid "Facebook" +msgstr "" + +#: gtkrg.ui.h:126 +msgid "Fast loading ( half size image)" +msgstr "" + +#: gtkrg.ui.h:128 +msgid "File Open Behaviour:" +msgstr "" + +#: gtkrg.ui.h:129 +msgid "Filter" +msgstr "" + +#: gtkrg.ui.h:130 +msgid "Fine" +msgstr "" + +#: gtkrg.ui.h:131 +msgid "Fine Rotation" +msgstr "" + +#: gtkrg.ui.h:132 +msgid "First" +msgstr "" + +#: gtkrg.ui.h:133 +msgid "Flickr" +msgstr "" + +#: gtkrg.ui.h:134 +msgid "Flip Horizontally" +msgstr "" + +#: gtkrg.ui.h:135 +msgid "Flip Vertically" +msgstr "" + +#: gtkrg.ui.h:136 +msgid "Formats" +msgstr "" + +#: gtkrg.ui.h:137 +msgid "Forward" +msgstr "" + +#: gtkrg.ui.h:138 +msgid "From:" +msgstr "" + +#: gtkrg.ui.h:139 +msgid "Full image workflow (slow!)" +msgstr "" + +#: gtkrg.ui.h:140 +msgid "Full metadata list" +msgstr "" + +#: gtkrg.ui.h:141 +msgid "Fullscreen Gallery" +msgstr "" + +#: gtkrg.ui.h:142 +msgid "Fullscreen Image" +msgstr "" + +#: gtkrg.ui.h:143 +msgid "" +"Gamma/\n" +"mid-tones" +msgstr "" + +#: gtkrg.ui.h:145 +msgid "Gamma:" +msgstr "" + +#: gtkrg.ui.h:146 +msgid "Gimp" +msgstr "" + +#: gtkrg.ui.h:147 +msgid "Gradient:" +msgstr "" + +#: gtkrg.ui.h:148 +msgid "Gray-Scale" +msgstr "" + +#: gtkrg.ui.h:149 +msgid "Green" +msgstr "" + +#: gtkrg.ui.h:150 +msgid "Group:" +msgstr "" + +#: gtkrg.ui.h:151 +msgid "Gtk supported formats" +msgstr "" + +#: gtkrg.ui.h:152 +msgid "Highlight mode:" +msgstr "" + +#: gtkrg.ui.h:153 +msgid "Highlights" +msgstr "" + +#: gtkrg.ui.h:154 +msgid "History" +msgstr "" + +#: gtkrg.ui.h:155 +msgid "Horizontal" +msgstr "" + +#: gtkrg.ui.h:156 +msgid "Horizontal split" +msgstr "" + +#: gtkrg.ui.h:157 +msgid "Hue" +msgstr "" + +#: gtkrg.ui.h:158 +msgid "Ignore" +msgstr "" + +#: gtkrg.ui.h:160 +msgid "Input ICC Profile:" +msgstr "" + +#: gtkrg.ui.h:161 +msgid "Input profile info!" +msgstr "" + +#: gtkrg.ui.h:163 +msgid "" +"Insert a new name or a pattern to \n" +"rename multiple files progressively.\n" +"(e.g. IMG01.CR2, IMG02.CR2, IMG03.CR2,...,\n" +"or IMG_01.CR2, IMG_02.CR2, IMG_03.CR2,...,)" +msgstr "" + +#: gtkrg.ui.h:167 +msgid "Insert here the file extension you know to be supported by dcraw." +msgstr "" + +#: gtkrg.ui.h:169 +msgid "Interpolate RGGB as four colors" +msgstr "" + +#: gtkrg.ui.h:170 +msgid "Interpolation quality:" +msgstr "" + +#: gtkrg.ui.h:171 +msgid "Inverted Selection" +msgstr "" + +#: gtkrg.ui.h:172 +msgid "Iptc" +msgstr "" + +#: gtkrg.ui.h:173 +msgid "Keep" +msgstr "" + +#: gtkrg.ui.h:174 +msgid "Kelvin" +msgstr "" + +#: gtkrg.ui.h:175 +msgid "Keyword:" +msgstr "" + +#: gtkrg.ui.h:177 +msgid "Last" +msgstr "" + +#: gtkrg.ui.h:178 +msgid "Layout" +msgstr "" + +#: gtkrg.ui.h:179 +msgid "Layout 1" +msgstr "" + +#: gtkrg.ui.h:180 +msgid "Layout 2" +msgstr "" + +#: gtkrg.ui.h:181 +msgid "Layout 3" +msgstr "" + +#: gtkrg.ui.h:182 +msgid "Layout 4" +msgstr "" + +#: gtkrg.ui.h:183 +msgid "Lens Distortion" +msgstr "" + +#: gtkrg.ui.h:184 +msgid "Levels, gamma" +msgstr "" + +#: gtkrg.ui.h:185 +msgid "Linear" +msgstr "" + +#: gtkrg.ui.h:186 +msgid "Load Presets" +msgstr "" + +#: gtkrg.ui.h:187 +msgid "Logarithmic" +msgstr "" + +#: gtkrg.ui.h:188 +msgid "MByte" +msgstr "" + +#: gtkrg.ui.h:189 +msgid "Magenta" +msgstr "" + +#: gtkrg.ui.h:190 +msgid "Mail" +msgstr "" + +#: gtkrg.ui.h:191 +msgid "Mail Client" +msgstr "" + +#: gtkrg.ui.h:192 +msgid "Makernotes" +msgstr "" + +#: gtkrg.ui.h:193 +msgid "Manual tagging" +msgstr "" + +#: gtkrg.ui.h:194 +msgid "Manual white balance" +msgstr "" + +#: gtkrg.ui.h:195 +msgid "Max cache Size:" +msgstr "" + +#: gtkrg.ui.h:196 +msgid "Median Filter" +msgstr "" + +#: gtkrg.ui.h:197 +msgid "Median filter:" +msgstr "" + +#: gtkrg.ui.h:198 +msgid "Message:" +msgstr "" + +#: gtkrg.ui.h:199 +msgid "Metadata" +msgstr "" + +#: gtkrg.ui.h:200 +msgid "Mid-point" +msgstr "" + +#: gtkrg.ui.h:201 +msgid "Mid-tones contrast" +msgstr "" + +#: gtkrg.ui.h:202 +msgid "Monochrome" +msgstr "" + +#: gtkrg.ui.h:203 +msgid "Move to" +msgstr "" + +#: gtkrg.ui.h:204 +msgid "Multisession" +msgstr "" + +#: gtkrg.ui.h:205 +msgid "Name:" +msgstr "" + +#: gtkrg.ui.h:206 +msgid "Needs restarting!" +msgstr "" + +#: gtkrg.ui.h:208 +msgid "New" +msgstr "" + +#: gtkrg.ui.h:210 +msgid "New Keyword" +msgstr "" + +#: gtkrg.ui.h:211 +msgid "New Tag" +msgstr "" + +#: gtkrg.ui.h:212 +msgid "New tag" +msgstr "" + +#: gtkrg.ui.h:213 +msgid "" +"Next Image\n" +"Previous Image\n" +"First Image\n" +"Last Image\n" +"Zoom In\n" +"Zoom Out\n" +"Zoom 1:1\n" +"Zoom Fit\n" +"Rotate Left\n" +"Rotate Right\n" +"Select All\n" +"Unselect\n" +"Mail\n" +"Burn CD/DVD\n" +"Invert Selection\n" +"Fullscreen\n" +"Slideshow\n" +"Open Folder\n" +"Open Album\n" +"Save As\n" +"Copy To\n" +"Move To\n" +"New Album\n" +"Print\n" +"Tag Manager\n" +"Add to Album\n" +"Layout 1\n" +"Layout 2\n" +"Layout 3\n" +"Layout 4\n" +"Fullscreen gallery mode\n" +"Quit\n" +"Delete\t\n" +"Close Development Window " +msgstr "" + +#: gtkrg.ui.h:248 +msgid "Oil Paint" +msgstr "" + +#: gtkrg.ui.h:250 +msgid "Open Folder" +msgstr "" + +#: gtkrg.ui.h:251 +msgid "Open album" +msgstr "" + +#: gtkrg.ui.h:252 +msgid "Open on development window" +msgstr "" + +#: gtkrg.ui.h:253 +msgid "Open with..." +msgstr "" + +#: gtkrg.ui.h:254 +msgid "Other..." +msgstr "" + +#: gtkrg.ui.h:255 +msgid "Output ICC Profile:" +msgstr "" + +#: gtkrg.ui.h:256 +msgid "Output Rendering Intent" +msgstr "" + +#: gtkrg.ui.h:257 +msgid "Output colorspace:" +msgstr "" + +#: gtkrg.ui.h:258 +msgid "Output profile Info!" +msgstr "" + +#: gtkrg.ui.h:259 +msgid "Overwrite all" +msgstr "" + +#: gtkrg.ui.h:260 publisher.ui.h:15 +msgid "Password:" +msgstr "" + +#: gtkrg.ui.h:261 +msgid "Picasa Web Albums" +msgstr "" + +#: gtkrg.ui.h:262 +msgid "Pick black point" +msgstr "" + +#: gtkrg.ui.h:263 +msgid "Pick gray point" +msgstr "" + +#: gtkrg.ui.h:264 +msgid "Pick white point" +msgstr "" + +#: gtkrg.ui.h:265 +msgid "Pincussion" +msgstr "" + +#: gtkrg.ui.h:266 +msgid "Port:" +msgstr "" + +#: gtkrg.ui.h:268 +msgid "Presets" +msgstr "" + +#: gtkrg.ui.h:269 +msgid "Press ESC to exit fullscreen" +msgstr "" + +#: gtkrg.ui.h:270 +msgid "Print" +msgstr "" + +#: gtkrg.ui.h:271 publisher.ui.h:17 +msgid "Publish" +msgstr "" + +#: gtkrg.ui.h:272 +msgid "Publish to..." +msgstr "" + +#: gtkrg.ui.h:273 +msgid "Quality" +msgstr "" + +#: gtkrg.ui.h:274 +msgid "Red" +msgstr "" + +#: gtkrg.ui.h:275 +msgid "Red Eye Removal" +msgstr "" + +#: gtkrg.ui.h:276 +msgid "Redo Workflow" +msgstr "" + +#: gtkrg.ui.h:277 +msgid "Reduce Noise" +msgstr "" + +#: gtkrg.ui.h:278 +msgid "Reduce Speckle Noise" +msgstr "" + +#: gtkrg.ui.h:279 +msgid "Remember last visited path" +msgstr "" + +#: gtkrg.ui.h:280 +msgid "Remove Keywords" +msgstr "" + +#: gtkrg.ui.h:281 +msgid "Remove all checked items?" +msgstr "" + +#: gtkrg.ui.h:282 +msgid "Remove all keywords from selected images" +msgstr "" + +#: gtkrg.ui.h:283 +msgid "Remove checked items" +msgstr "" + +#: gtkrg.ui.h:284 +msgid "Remove checked keywords from selected images" +msgstr "" + +#: gtkrg.ui.h:285 +msgid "Remove row" +msgstr "" + +#: gtkrg.ui.h:286 +msgid "Remove selected keyword from list" +msgstr "" + +#: gtkrg.ui.h:287 +msgid "Remove selected row" +msgstr "" + +#: gtkrg.ui.h:288 +msgid "Remove tag" +msgstr "" + +#: gtkrg.ui.h:289 +msgid "Rename" +msgstr "" + +#: gtkrg.ui.h:290 +msgid "Rename Images" +msgstr "" + +#: gtkrg.ui.h:292 +msgid "Reset" +msgstr "" + +#: gtkrg.ui.h:293 +msgid "Reset All Channels" +msgstr "" + +#: gtkrg.ui.h:294 +msgid "Reset Workflow" +msgstr "" + +#: gtkrg.ui.h:295 +msgid "Reset all channels" +msgstr "" + +#: gtkrg.ui.h:296 +msgid "Reset channel" +msgstr "" + +#: gtkrg.ui.h:297 +msgid "Reset channels" +msgstr "" + +#: gtkrg.ui.h:298 +msgid "Reset workflow?" +msgstr "" + +#: gtkrg.ui.h:299 +msgid "Root directory" +msgstr "" + +#: gtkrg.ui.h:300 +msgid "Rotate Left" +msgstr "" + +#: gtkrg.ui.h:301 +msgid "Rotate Right" +msgstr "" + +#: gtkrg.ui.h:302 +msgid "Rotate image:" +msgstr "" + +#: gtkrg.ui.h:303 +msgid "Rounded corners" +msgstr "" + +#: gtkrg.ui.h:304 +msgid "Saturation" +msgstr "" + +#: gtkrg.ui.h:305 +msgid "Save As" +msgstr "" + +#: gtkrg.ui.h:306 +msgid "Save In:" +msgstr "" + +#: gtkrg.ui.h:307 +msgid "Save Presets" +msgstr "" + +#: gtkrg.ui.h:308 +msgid "Save Template" +msgstr "" + +#: gtkrg.ui.h:309 +msgid "Save curve" +msgstr "" + +#: gtkrg.ui.h:310 +msgid "Save curve preset" +msgstr "" + +#: gtkrg.ui.h:311 +msgid "Save in:" +msgstr "" + +#: gtkrg.ui.h:315 +msgid "Search" +msgstr "" + +#: gtkrg.ui.h:316 +msgid "Select A Folder" +msgstr "" + +#: gtkrg.ui.h:317 +msgid "Select All" +msgstr "" + +#: gtkrg.ui.h:318 +msgid "Select Profile" +msgstr "" + +#: gtkrg.ui.h:319 +msgid "Selection Color" +msgstr "" + +#: gtkrg.ui.h:320 +msgid "Seleziona una cartella" +msgstr "" + +#: gtkrg.ui.h:321 +msgid "Send" +msgstr "" + +#: gtkrg.ui.h:322 +msgid "Sepia Tone" +msgstr "" + +#: gtkrg.ui.h:323 +msgid "Server:" +msgstr "" + +#: gtkrg.ui.h:324 +msgid "Set as album preview" +msgstr "" + +#: gtkrg.ui.h:325 +msgid "Set pause in seconds" +msgstr "" + +#: gtkrg.ui.h:326 +msgid "Set white balance:" +msgstr "" + +#: gtkrg.ui.h:327 +msgid "Shadows" +msgstr "" + +#: gtkrg.ui.h:329 +msgid "Shortcuts" +msgstr "" + +#: gtkrg.ui.h:330 +msgid "Show a tooltip with image info when the pointer is over the thumbnail" +msgstr "" + +#: gtkrg.ui.h:331 +msgid "Show debugging output" +msgstr "" + +#: gtkrg.ui.h:332 +msgid "Show grid" +msgstr "" + +#: gtkrg.ui.h:333 +msgid "Show hidden folders" +msgstr "" + +#: gtkrg.ui.h:334 +msgid "Show over exposed pixels" +msgstr "" + +#: gtkrg.ui.h:335 +msgid "Show under exposed pixels" +msgstr "" + +#: gtkrg.ui.h:336 +msgid "Simulates output device on your display" +msgstr "" + +#: gtkrg.ui.h:338 +msgid "Slideshow" +msgstr "" + +#: gtkrg.ui.h:339 +msgid "Soft proofing" +msgstr "" + +#: gtkrg.ui.h:340 +msgid "Sort by" +msgstr "" + +#: gtkrg.ui.h:342 +msgid "Square thumbnails" +msgstr "" + +#: gtkrg.ui.h:343 +msgid "Start batch processor" +msgstr "" + +#: gtkrg.ui.h:344 +msgid "Strip Profiles" +msgstr "" + +#: gtkrg.ui.h:345 +msgid "Styles" +msgstr "" + +#: gtkrg.ui.h:346 +msgid "Subject:" +msgstr "" + +#: gtkrg.ui.h:347 +msgid "Subtract dark frame" +msgstr "" + +#: gtkrg.ui.h:348 +msgid "Synchronize Tags Database" +msgstr "" + +#: gtkrg.ui.h:349 +msgid "Synchronize database" +msgstr "" + +#: gtkrg.ui.h:350 +msgid "Tag Manager" +msgstr "" + +#: gtkrg.ui.h:351 +msgid "Tag Manager Manual" +msgstr "" + +#: gtkrg.ui.h:352 +msgid "Tag manager" +msgstr "" + +#: gtkrg.ui.h:353 +msgid "Tag:" +msgstr "" + +#: gtkrg.ui.h:355 +msgid "Temperature" +msgstr "" + +#: gtkrg.ui.h:356 +msgid "Template:" +msgstr "" + +#: gtkrg.ui.h:357 +msgid "" +"The Tag Manager is a powerfull tool permitting to edit metadata information " +"of selected images. \n" +"It consist on two pages: Keywords and Advanced Metadata Editor.\n" +"\n" +"The \"Keywords\" page is for fast keywords management. It contains a generic " +"list\n" +"of categories that can be costumized as you like.\n" +"The \"New Keyword\" button adds a new keyword to the list;\n" +"The \"Remove Keywords\" button removes all checked keywords from selected " +"images;\n" +"The \"Clear Keywords\" removes all keywords from selected images.\n" +"To add keywords, check any keyword from list and click \"Apply Tags\".\n" +"Keep in mind that to write image metadata the \"write iptc metadata\" " +"option\n" +"on GTKRawGallery preferences has to be checked, otherwise keywords will be " +"stored\n" +"only into the Sqlite database for fast search. That can be the right choice\n" +"for users not interested to keywords portability.\n" +"\n" +"The \"Advanced Metadata Editor\" was designed thinking to advanced users\n" +"and permits to edit a huge list of metadata. Exif, Iptc and several Xmp " +"groups are supported.\n" +"To build a list of tags:\n" +"1) Select a group;\n" +"2) Select a tag;\n" +"3) Click \"Add\";\n" +"Repeate again to add new tags.\n" +"Finally click \"Apply Tags\" to write metadata.\n" +"Unlike keyword tagging, metadata is always written!\n" +"To avoid rebuilding the same tag list, you can save it as a template to " +"quickly reloading later." +msgstr "" + +#: gtkrg.ui.h:383 +msgid "Thumbnails" +msgstr "" + +#: gtkrg.ui.h:384 +msgid "Thumbnails Autorotation" +msgstr "" + +#: gtkrg.ui.h:386 +msgid "To:" +msgstr "" + +#: gtkrg.ui.h:387 +msgid "Tone" +msgstr "" + +#: gtkrg.ui.h:388 +msgid "Total size:" +msgstr "" + +#: gtkrg.ui.h:389 +msgid "Transform" +msgstr "" + +#: gtkrg.ui.h:390 +msgid "Ts:" +msgstr "" + +#: gtkrg.ui.h:391 +msgid "Type:" +msgstr "" + +#: gtkrg.ui.h:392 +msgid "Undo Workflow" +msgstr "" + +#: gtkrg.ui.h:393 +msgid "Uniform border" +msgstr "" + +#: gtkrg.ui.h:394 +msgid "Unselect" +msgstr "" + +#: gtkrg.ui.h:395 +msgid "Unsharp Mask" +msgstr "" + +#: gtkrg.ui.h:396 +msgid "Unsplit" +msgstr "" + +#: gtkrg.ui.h:397 +msgid "Upload to DropBox" +msgstr "" + +#: gtkrg.ui.h:398 +msgid "Use embedded color matrix" +msgstr "" + +#: gtkrg.ui.h:399 +msgid "User:" +msgstr "" + +#: gtkrg.ui.h:400 +msgid "Vertical" +msgstr "" + +#: gtkrg.ui.h:401 +msgid "Vertical split" +msgstr "" + +#: gtkrg.ui.h:402 +msgid "Vignette Removal" +msgstr "" + +#: gtkrg.ui.h:404 +msgid "White Balance" +msgstr "" + +#: gtkrg.ui.h:405 +msgid "Workflow" +msgstr "" + +#: gtkrg.ui.h:406 +msgid "Write keywords as iptc metadata" +msgstr "" + +#: gtkrg.ui.h:407 +msgid "Write metadata" +msgstr "" + +#: gtkrg.ui.h:408 +msgid "Xmp" +msgstr "" + +#: gtkrg.ui.h:409 +msgid "Yellow" +msgstr "" + +#: gtkrg.ui.h:410 +msgid "Zoom 100" +msgstr "" + +#: gtkrg.ui.h:411 +msgid "Zoom Fit" +msgstr "" + +#: gtkrg.ui.h:412 +msgid "Zoom In" +msgstr "" + +#: gtkrg.ui.h:413 +msgid "Zoom Out" +msgstr "" + +#: gtkrg.ui.h:414 +msgid "_Album" +msgstr "" + +#: gtkrg.ui.h:415 +msgid "_Edit" +msgstr "" + +#: gtkrg.ui.h:416 +msgid "_File" +msgstr "" + +#: gtkrg.ui.h:417 +msgid "_Help" +msgstr "" + +#: gtkrg.ui.h:418 +msgid "_Options" +msgstr "" + +#: gtkrg.ui.h:419 +msgid "_Tags" +msgstr "" + +#: gtkrg.ui.h:420 +msgid "_View" +msgstr "" + +#: gtkrg.ui.h:421 +msgid "amount" +msgstr "" + +#: gtkrg.ui.h:422 +msgid "angle" +msgstr "" + +#: gtkrg.ui.h:423 +msgid "apply tags" +msgstr "" + +#: gtkrg.ui.h:424 +msgid "blue:" +msgstr "" + +#: gtkrg.ui.h:425 +msgid "bmp" +msgstr "" + +#: gtkrg.ui.h:426 +msgid "by album" +msgstr "" + +#: gtkrg.ui.h:427 +msgid "by folder" +msgstr "" + +#: gtkrg.ui.h:428 +msgid "current height" +msgstr "" + +#: gtkrg.ui.h:429 +msgid "current width" +msgstr "" + +#: gtkrg.ui.h:430 +msgid "custom" +msgstr "" + +#: gtkrg.ui.h:431 +msgid "date" +msgstr "" + +#: gtkrg.ui.h:432 +msgid "default settings" +msgstr "" + +#: gtkrg.ui.h:433 +msgid "dither" +msgstr "" + +#: gtkrg.ui.h:434 +msgid "frames:" +msgstr "" + +#: gtkrg.ui.h:435 +msgid "fullscreen" +msgstr "" + +#: gtkrg.ui.h:436 +msgid "insert colon separated tags" +msgstr "" + +#: gtkrg.ui.h:437 +msgid "insert colon separated tags and click apply" +msgstr "" + +#: gtkrg.ui.h:438 +msgid "jpg" +msgstr "" + +#: gtkrg.ui.h:439 +msgid "label" +msgstr "" + +#: gtkrg.ui.h:440 +msgid "levels" +msgstr "" + +#: gtkrg.ui.h:441 +msgid "minimize column spacings" +msgstr "" + +#: gtkrg.ui.h:442 +msgid "multiple search" +msgstr "" + +#: gtkrg.ui.h:443 +msgid "multipliers" +msgstr "" + +#: gtkrg.ui.h:444 +msgid "name" +msgstr "" + +#: gtkrg.ui.h:445 +msgid "only grayscale pixels" +msgstr "" + +#: gtkrg.ui.h:446 +msgid "page 1" +msgstr "" + +#: gtkrg.ui.h:447 +msgid "page 2" +msgstr "" + +#: gtkrg.ui.h:448 +msgid "page 3" +msgstr "" + +#: gtkrg.ui.h:449 +msgid "page 4" +msgstr "" + +#: gtkrg.ui.h:450 +msgid "png" +msgstr "" + +#: gtkrg.ui.h:451 +msgid "preserve ratio" +msgstr "" + +#: gtkrg.ui.h:452 +msgid "radius" +msgstr "" + +#: gtkrg.ui.h:453 +msgid "random" +msgstr "" + +#: gtkrg.ui.h:454 +msgid "ratio" +msgstr "" + +#: gtkrg.ui.h:455 +msgid "red:" +msgstr "" + +#: gtkrg.ui.h:456 +msgid "remove from list" +msgstr "" + +#: gtkrg.ui.h:457 +msgid "remove preset" +msgstr "" + +#: gtkrg.ui.h:458 +msgid "remove style" +msgstr "" + +#: gtkrg.ui.h:459 +msgid "reverse" +msgstr "" + +#: gtkrg.ui.h:460 +msgid "save new preset" +msgstr "" + +#: gtkrg.ui.h:461 +msgid "save preset changes" +msgstr "" + +#: gtkrg.ui.h:463 +msgid "save style changes" +msgstr "" + +#: gtkrg.ui.h:464 +msgid "scan subfolders" +msgstr "" + +#: gtkrg.ui.h:465 +msgid "show grid" +msgstr "" + +#: gtkrg.ui.h:466 +msgid "sigma" +msgstr "" + +#: gtkrg.ui.h:467 +msgid "size" +msgstr "" + +#: gtkrg.ui.h:468 +msgid "threshold" +msgstr "" + +#: gtkrg.ui.h:469 +msgid "tiff 16 bit" +msgstr "" + +#: gtkrg.ui.h:470 +msgid "tiff 8 bit" +msgstr "" + +#: gtkrg.ui.h:471 +msgid "tiff best" +msgstr "" + +#: gtkrg.ui.h:472 +msgid "to" +msgstr "" + +#: gtkrg.ui.h:473 +msgid "toolbutton13" +msgstr "" + +#: gtkrg.ui.h:474 +msgid "toolbutton36" +msgstr "" + +#: gtkrg.ui.h:475 +msgid "toolbutton47" +msgstr "" + +#: gtkrg.ui.h:476 +msgid "toolbutton48" +msgstr "" + +#: gtkrg.ui.h:477 +msgid "type" +msgstr "" + +#: gtkrg.ui.h:478 +msgid "type:" +msgstr "" + +#: gtkrg.ui.h:479 +msgid "use sicure authentication" +msgstr "" + +#: print.ui.h:1 +msgid "Margins" +msgstr "" + +#: print.ui.h:2 +msgid "Paper" +msgstr "" + +#: print.ui.h:3 +msgid "Preview" +msgstr "" + +#: print.ui.h:4 +msgid "Bottom:" +msgstr "" + +#: print.ui.h:5 +msgid "Custom" +msgstr "" + +#: print.ui.h:6 +msgid "Format:" +msgstr "" + +#: print.ui.h:7 +msgid "Landscape" +msgstr "" + +#: print.ui.h:8 +msgid "Left:" +msgstr "" + +#: print.ui.h:9 +msgid "Orientation:" +msgstr "" + +#: print.ui.h:10 +msgid "Portrait" +msgstr "" + +#: print.ui.h:11 +msgid "Right:" +msgstr "" + +#: print.ui.h:12 +msgid "Scale:" +msgstr "" + +#: print.ui.h:13 +msgid "Size:" +msgstr "" + +#: print.ui.h:14 +msgid "Top:" +msgstr "" + +#: print.ui.h:15 +msgid "Unit:" +msgstr "" + +#: print.ui.h:16 +msgid "height:" +msgstr "" + +#: print.ui.h:17 +msgid "width:" +msgstr "" + +#: publisher.ui.h:1 +msgid "Album description" +msgstr "" + +#: publisher.ui.h:2 +msgid "Album privacy" +msgstr "" + +#: publisher.ui.h:3 +msgid "Autoscale" +msgstr "" + +#: publisher.ui.h:4 +msgid "Autoscale images to max 720 pixels preserving aspect ratio " +msgstr "" + +#: publisher.ui.h:6 +msgid "Create a new album" +msgstr "" + +#: publisher.ui.h:7 +msgid "Description:" +msgstr "" + +#: publisher.ui.h:8 +msgid "E-mail:" +msgstr "" + +#: publisher.ui.h:9 +msgid "Facebook username:" +msgstr "" + +#: publisher.ui.h:11 +msgid "Log-in as different user" +msgstr "" + +#: publisher.ui.h:12 +msgid "Login" +msgstr "" + +#: publisher.ui.h:14 +msgid "New login" +msgstr "" + +#: publisher.ui.h:16 +msgid "Privacy:" +msgstr "" + +#: publisher.ui.h:18 +msgid "Upload into" +msgstr "" + +#: publisher.ui.h:19 +msgid "Upload into an existing album" +msgstr "" + +#: publisher.ui.h:20 +msgid "commentable" +msgstr "" + +#: publisher.ui.h:21 +msgid "enable album commenting" +msgstr "" + +#: publisher.ui.h:22 +msgid "public" +msgstr "" diff -Nru gtkrawgallery-0.9.8/setup.py gtkrawgallery-0.9.9/setup.py --- gtkrawgallery-0.9.8/setup.py 2013-03-21 04:03:33.000000000 +0000 +++ gtkrawgallery-0.9.9/setup.py 2013-09-27 13:39:39.000000000 +0000 @@ -1,8 +1,8 @@ #!/usr/bin/env python -# -*- coding: cp1252 -*- +# -*- coding: utf-8 -*- ''' description: GTKRawGallery Linux setup; -copyright 2013 Daniele Isca +copyright © 2013 Daniele Isca license: GNU GPL v.3 (see file COPYING.txt); This software comes with no warranty of any kind, use at your own risk! ''' @@ -11,22 +11,29 @@ import os.path import glob -if os.path.isdir('/usr/local/share/gtkrawgallery'): - datadir = '/usr/local/share/gtkrawgallery' +if os.path.isdir('/usr/share/gtkrawgallery'): + datadir = '/usr/share/gtkrawgallery' elif os.path.isdir('/usr/share/gtkrawgallery'): datadir = '/usr/share/gtkrawgallery' else: datadir = False if datadir: try: - print 'cleaning the system from old data files...' + print 'cleaning the system from older data files...' shutil.rmtree(datadir, ignore_errors=True) print 'removing %s ......done!' % datadir except: pass +if os.path.isfile('/usr/local/share/locale/it/LC_MESSAGES/gtkrawgallery.mo'): + os.remove('/usr/local/share/locale/it/LC_MESSAGES/gtkrawgallery.mo') + print 'removing %s ......done!' % '/usr/local/share/locale/it/LC_MESSAGES/gtkrawgallery.mo' +if os.path.isfile('/usr/share/locale/it/LC_MESSAGES/gtkrawgallery.mo'): + os.remove('/usr/share/locale/it/LC_MESSAGES/gtkrawgallery.mo') + print 'removing %s ......done!' % '/usr/share/locale/it/LC_MESSAGES/gtkrawgallery.mo' + setup(name='GTKRawGallery', - version='0.9.7', + version='0.9.9', description='A workflow oriented photo manager for digital camera raw image development', author='Daniele Isca', author_email='bit123@users.sourceforge.net', @@ -89,21 +96,22 @@ 'gdata.youtube', ], package_dir = {'gdata':'src/gdata', 'atom':'src/atom'}, - data_files=[('share/gtkrawgallery', ['README.txt', 'COPYING.txt', 'CHANGELOG.txt', 'messages.pot', 'src/wlist.db', - 'src/gtkrg_smtp.py', 'src/exif.py', 'src/gtkrg_profiles.py', 'src/gtkrg_sqlite.py', 'src/gtkrg_print.py', + data_files=[('share/gtkrawgallery', ['README.txt', 'COPYING.txt', 'CHANGELOG.txt', 'src/wlist.db', 'src/intl.py', + 'src/gtkrg_smtp.py', 'src/exif.py', 'src/gtkrg_profiles.py', 'src/gtkrg_sqlite.py', 'src/gtkrg_print.py', 'src/webview.ui', 'src/gtkrg.ui', 'src/splash.ui', 'src/print.ui', 'src/publisher.ui', 'src/info.ui', 'src/gtkrg_loader.py', 'src/gtkrg_dropbox.py', 'src/gtkrg_facebook.py', 'src/gtkrg_flickr.py', 'src/gtkrg_picasaweb.py', 'src/facebook.py', 'src/gtkrc', 'src/exiftool.py', 'src/curve.py']), ('share/gtkrawgallery/icons', glob.glob('src/icons/*')), ('share/gtkrawgallery/pythonmagickwand', glob.glob('src/pythonmagickwand/*')), - ('share/gtkrawgallery/mechanize', glob.glob('src/mechanize/*')), ('share/gtkrawgallery/flickrapi', glob.glob('src/flickrapi/*')), - ('share/gtkrawgallery/dbupload', glob.glob('src/dbupload/*')), + ('share/gtkrawgallery/dropbox', glob.glob('src/dropbox/*')), ('share/applications', ['src/gtkrawgallery.desktop']), - # ('share/locale/it/LC_MESSAGES', ['locale/it/LC_MESSAGES/gtkrawgallery.mo']), + ('share/locale/it/LC_MESSAGES', ['locale/it/LC_MESSAGES/gtkrawgallery.mo']), ('share/pixmaps', ['src/icons/gtkrawgallery.png'])], ) -try: - shutil.rmtree("build", ignore_errors=True) -except: - pass +#if os.path.isdir('/usr/share/locale/it/LC_MESSAGES'): +# shutil.copy('locale/it/LC_MESSAGES/gtkrawgallery.mo', '/usr/share/locale/it/LC_MESSAGES') #workaround for ubuntu +if os.path.isdir('build'): + shutil.rmtree('build', ignore_errors=True) + print "removing build directory ......done!" + diff -Nru gtkrawgallery-0.9.8/setup.py~ gtkrawgallery-0.9.9/setup.py~ --- gtkrawgallery-0.9.8/setup.py~ 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/setup.py~ 2013-09-24 16:02:31.000000000 +0000 @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +''' +description: GTKRawGallery Linux setup; +copyright © 2013 Daniele Isca +license: GNU GPL v.3 (see file COPYING.txt); +This software comes with no warranty of any kind, use at your own risk! +''' +from distutils.core import setup +import shutil +import os.path +import glob + +if os.path.isdir('/usr/share/gtkrawgallery'): + datadir = '/usr/share/gtkrawgallery' +elif os.path.isdir('/usr/share/gtkrawgallery'): + datadir = '/usr/share/gtkrawgallery' +else: + datadir = False +if datadir: + try: + print 'cleaning the system from older data files...' + shutil.rmtree(datadir, ignore_errors=True) + print 'removing %s ......done!' % datadir + except: + pass +if os.path.isfile('/usr/local/share/locale/it/LC_MESSAGES/gtkrawgallery.mo'): + os.remove('/usr/local/share/locale/it/LC_MESSAGES/gtkrawgallery.mo') + print 'removing %s ......done!' % '/usr/local/share/locale/it/LC_MESSAGES/gtkrawgallery.mo' +if os.path.isfile('/usr/share/locale/it/LC_MESSAGES/gtkrawgallery.mo'): + os.remove('/usr/share/locale/it/LC_MESSAGES/gtkrawgallery.mo') + print 'removing %s ......done!' % '/usr/share/locale/it/LC_MESSAGES/gtkrawgallery.mo' + + +setup(name='GTKRawGallery', + version='0.9.9', + description='A workflow oriented photo manager for digital camera raw image development', + author='Daniele Isca', + author_email='bit123@users.sourceforge.net', + url='http://sourceforge.net/projects/gtkrawgallery/', + classifiers=[ + 'Environment :: X11 Applications', + 'Intended Audience :: End Users/Desktop', + 'License :: GNU General Public License (GPL v.3)', + 'Operating System :: Linux :: Windows', + 'Programming Language :: Python', + 'Topic :: Multimedia :: Graphics :: Photography'], + py_modules=['gtkrawgallery', 'uninstall_gtkrawgallery'], + scripts=['gtkrawgallery', 'uninstall-gtkrawgallery'], + packages=[ + 'atom', + 'gdata', + 'gdata.Crypto', + 'gdata.Crypto.Cipher', + 'gdata.Crypto.Hash', + 'gdata.Crypto.Protocol', + 'gdata.Crypto.PublicKey', + 'gdata.Crypto.Util', + 'gdata.acl', + 'gdata.alt', + 'gdata.analytics', + 'gdata.apps', + 'gdata.apps.adminsettings', + 'gdata.apps.audit', + 'gdata.apps.emailsettings', + 'gdata.apps.groups', + 'gdata.apps.migration', + 'gdata.apps.multidomain', + 'gdata.apps.organization', + 'gdata.blogger', + 'gdata.books', + 'gdata.calendar', + 'gdata.calendar_resource', + 'gdata.codesearch', + 'gdata.contacts', + 'gdata.contentforshopping', + 'gdata.docs', + 'gdata.dublincore', + 'gdata.exif', + 'gdata.finance', + 'gdata.geo', + 'gdata.health', + 'gdata.media', + 'gdata.notebook', + 'gdata.oauth', + 'gdata.opensearch', + 'gdata.photos', + 'gdata.projecthosting', + 'gdata.sites', + 'gdata.spreadsheet', + 'gdata.spreadsheets', + 'gdata.tlslite', + 'gdata.tlslite.integration', + 'gdata.tlslite.utils', + 'gdata.webmastertools', + 'gdata.youtube', + ], + package_dir = {'gdata':'src/gdata', 'atom':'src/atom'}, + data_files=[('share/gtkrawgallery', ['README.txt', 'COPYING.txt', 'CHANGELOG.txt', 'src/wlist.db', 'src/intl.py', + 'src/gtkrg_smtp.py', 'src/exif.py', 'src/gtkrg_profiles.py', 'src/gtkrg_sqlite.py', 'src/gtkrg_print.py', 'src/webview.ui', + 'src/gtkrg.ui', 'src/splash.ui', 'src/print.ui', 'src/publisher.ui', 'src/info.ui', 'src/gtkrg_loader.py', 'src/gtkrg_dropbox.py', + 'src/gtkrg_facebook.py', 'src/gtkrg_flickr.py', 'src/gtkrg_picasaweb.py', 'src/facebook.py', 'src/gtkrc', 'src/exiftool.py', 'src/curve.py']), + ('share/gtkrawgallery/icons', glob.glob('src/icons/*')), + ('share/gtkrawgallery/pythonmagickwand', glob.glob('src/pythonmagickwand/*')), + ('share/gtkrawgallery/flickrapi', glob.glob('src/flickrapi/*')), + ('share/gtkrawgallery/dropbox', glob.glob('src/dropbox/*')), + ('share/applications', ['src/gtkrawgallery.desktop']), + ('share/locale/it/LC_MESSAGES', ['locale/it/LC_MESSAGES/gtkrawgallery.mo']), + ('share/pixmaps', ['src/icons/gtkrawgallery.png'])], + ) + +if os.path.isdir('/usr/share/locale/it/LC_MESSAGES'): + shutil.copy('locale/it/LC_MESSAGES/gtkrawgallery.mo', '/usr/share/locale/it/LC_MESSAGES') #workaround for ubuntu +if os.path.isdir('build'): + shutil.rmtree('build', ignore_errors=True) + print "removing build directory ......done!" + diff -Nru gtkrawgallery-0.9.8/src/COPYING.txt gtkrawgallery-0.9.9/src/COPYING.txt --- gtkrawgallery-0.9.8/src/COPYING.txt 2013-03-21 05:08:35.000000000 +0000 +++ gtkrawgallery-0.9.9/src/COPYING.txt 2011-11-23 10:28:42.000000000 +0000 @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff -Nru gtkrawgallery-0.9.8/src/atom/__init__.py gtkrawgallery-0.9.9/src/atom/__init__.py --- gtkrawgallery-0.9.8/src/atom/__init__.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/atom/__init__.py 2013-09-24 08:49:58.000000000 +0000 @@ -53,7 +53,8 @@ try: from xml.etree import ElementTree except ImportError: - from elementtree import ElementTree + pass +## from elementtree import ElementTree import warnings diff -Nru gtkrawgallery-0.9.8/src/dbupload/__init__.py gtkrawgallery-0.9.9/src/dbupload/__init__.py --- gtkrawgallery-0.9.8/src/dbupload/__init__.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dbupload/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -import dbupload - -from upload import upload_file -from dbconn import DropboxConnection - -__all__ = ['upload_file','DropboxConnection'] \ No newline at end of file diff -Nru gtkrawgallery-0.9.8/src/dbupload/dbconn.py gtkrawgallery-0.9.9/src/dbupload/dbconn.py --- gtkrawgallery-0.9.8/src/dbupload/dbconn.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dbupload/dbconn.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,157 +0,0 @@ -import mechanize -import urllib2 -import re -import json - -class DropboxConnection: - """ Creates a connection to Dropbox """ - - email = "" - password = "" - root_ns = "" - token = "" - browser = None - - def __init__(self,email,password): - self.email = email - self.password = password - - self.login() - self.get_constants() - - def login(self): - """ Login to Dropbox and return mechanize browser instance """ - - # Fire up a browser using mechanize - self.browser = mechanize.Browser() - self.browser.addheaders = [("User-agent", "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1")] - self.browser.set_handle_robots(False) - - # Browse to the login page - self.browser.open('https://www.dropbox.com/login') - - # Enter the username and password into the login form - isLoginForm = lambda l: l.action == "https://www.dropbox.com/login" and l.method == "POST" - - try: - self.browser.select_form(predicate=isLoginForm) - except: - self.browser = None - raise(Exception('Unable to find login form')) - - self.browser['login_email'] = self.email - self.browser['login_password'] = self.password - - # Send the form - response = self.browser.submit() - - def get_constants(self): - """ Load constants from page """ - - home_src = self.browser.open('https://www.dropbox.com/home').read() - - try: - self.root_ns = re.findall(r"root_ns: (\d+)", home_src)[0] - self.token = re.findall(r"TOKEN: '(.+?)'", home_src)[0].decode('string_escape') - - except: - raise(Exception("Unable to find constants for AJAX requests")) - - def upload_file(self,local_file,remote_dir,remote_file): - """ Upload a local file to Dropbox """ - - if(not self.is_logged_in()): - raise(Exception("Can't upload when not logged in")) - - self.browser.open('https://www.dropbox.com/') - - # Add our file upload to the upload form - isUploadForm = lambda u: u.action == "https://dl-web.dropbox.com/upload" and u.method == "POST" - - try: - self.browser.select_form(predicate=isUploadForm) - except: - raise(Exception('Unable to find upload form')) - - self.browser.form.find_control("dest").readonly = False - self.browser.form.set_value(remote_dir,"dest") - self.browser.form.add_file(open(local_file,"rb"),"",remote_file) - - # Submit the form with the file - self.browser.submit() - - def get_dir_list(self,remote_dir): - """ Get file info for a directory """ - - if(not self.is_logged_in()): - raise(Exception("Can't download when not logged in")) - - req_vars = "ns_id="+self.root_ns+"&referrer=&t="+self.token - - req = urllib2.Request('https://www.dropbox.com/browse'+remote_dir,data=req_vars) - req.add_header('Referer', 'https://www.dropbox.com/home'+remote_dir) - - dir_info = json.loads(self.browser.open(req).read()) - - dir_list = [] - - for item in dir_info['file_info']: - # Eliminate directories - if(item[0] == True): - # get local filename - absolute_filename = item[3] - local_filename = re.findall(r".*\/(.*)", absolute_filename)[0] - - # get file URL and add it to the dictionary - - dir_list.append(local_filename) - - return dir_list - - def get_file_list(self,remote_dir): - """ Get file info for a directory """ - - if(not self.is_logged_in()): - raise(Exception("Can't download when not logged in")) - - req_vars = "ns_id="+self.root_ns+"&referrer=&t="+self.token - - req = urllib2.Request('https://www.dropbox.com/browse'+remote_dir,data=req_vars) - req.add_header('Referer', 'https://www.dropbox.com/home'+remote_dir) - - dir_info = json.loads(self.browser.open(req).read()) - - filelist = [] - - for item in dir_info['file_info']: - # Eliminate directories - if(item[0] == False): - # get local filename - absolute_filename = item[3] - local_filename = re.findall(r".*\/(.*)", absolute_filename)[0] - filelist.append(local_filename) - - # get file URL and add it to the dictionary -## file_url = item[8] -## dir_list[local_filename] = file_url - - return filelist - - def get_download_url(self, remote_dir, remote_file): - """ Get the URL to download a file """ - - return self.get_dir_list(remote_dir)[remote_file] - - def download_file(self, remote_dir, remote_file, local_file): - """ Download a file and save it locally """ - - fh = open(local_file, "wb") - fh.write(self.browser.open(self.get_download_url(remote_dir,remote_file)).read()) - fh.close() - - def is_logged_in(self): - """ Checks if a login has been established """ - if(self.browser): - return True - else: - return False diff -Nru gtkrawgallery-0.9.8/src/dbupload/license.txt gtkrawgallery-0.9.9/src/dbupload/license.txt --- gtkrawgallery-0.9.8/src/dbupload/license.txt 2013-03-21 05:08:38.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dbupload/license.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -Copyright (c) 2010, Jon Craton -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. Redistributions in -binary form must reproduce the above copyright notice, this list of -conditions and the following disclaimer in the documentation and/or other -materials provided with the distribution. Neither the name of Jon Craton -nor the names of its contributors may be used to endorse or promote -products derived from this software without specific prior written -permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, -BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff -Nru gtkrawgallery-0.9.8/src/dbupload/upload.py gtkrawgallery-0.9.9/src/dbupload/upload.py --- gtkrawgallery-0.9.8/src/dbupload/upload.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dbupload/upload.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -import mechanize -from dbconn import DropboxConnection - -def upload_file(local_file,remote_dir,remote_file,email,password): - """ Upload a local file to Dropbox """ - - conn = DropboxConnection(email, password) - conn.upload_file(local_file,remote_dir,remote_file) diff -Nru gtkrawgallery-0.9.8/src/dropbox/LICENSE gtkrawgallery-0.9.9/src/dropbox/LICENSE --- gtkrawgallery-0.9.8/src/dropbox/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/LICENSE 2013-07-07 20:43:34.000000000 +0000 @@ -0,0 +1,20 @@ +Copyright (c) 2009-2011 Dropbox Inc., http://www.dropbox.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -Nru gtkrawgallery-0.9.8/src/dropbox/Makefile gtkrawgallery-0.9.9/src/dropbox/Makefile --- gtkrawgallery-0.9.8/src/dropbox/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/Makefile 2013-07-07 20:43:34.000000000 +0000 @@ -0,0 +1,132 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build -q +PAPER = +BUILDDIR = _build +SDK_VERSION ?= UNKNOWN + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +SDK_VERSION_OPT = -D version=$(SDK_VERSION) -D release=$(SDK_VERSION) +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SDK_VERSION_OPT) $(SPHINXOPTS) -c .. . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -E -n -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/dropbox.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/dropbox.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/dropbox" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/dropbox" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff -Nru gtkrawgallery-0.9.8/src/dropbox/__init__.py gtkrawgallery-0.9.9/src/dropbox/__init__.py --- gtkrawgallery-0.9.8/src/dropbox/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/__init__.py 2013-09-24 08:49:58.000000000 +0000 @@ -0,0 +1,3 @@ +from __future__ import absolute_import + +from . import client, rest, session diff -Nru gtkrawgallery-0.9.8/src/dropbox/client.py gtkrawgallery-0.9.9/src/dropbox/client.py --- gtkrawgallery-0.9.8/src/dropbox/client.py 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/client.py 2013-09-24 08:49:58.000000000 +0000 @@ -0,0 +1,1307 @@ +from __future__ import absolute_import + +import base64 +import re +import os +import sys +import urllib +from StringIO import StringIO +try: + import json +except ImportError: + import simplejson as json + +from .rest import ErrorResponse, RESTClient +from .session import BaseSession, DropboxSession, DropboxOAuth2Session + +def format_path(path): + """Normalize path for use with the Dropbox API. + + This function turns multiple adjacent slashes into single + slashes, then ensures that there's a leading slash but + not a trailing slash. + """ + if not path: + return path + + path = re.sub(r'/+', '/', path) + + if path == '/': + return (u"" if isinstance(path, unicode) else "") + else: + return '/' + path.strip('/') + +class DropboxClient(object): + """ + The class that lets you make Dropbox API calls. You'll need to obtain an + OAuth 2 access token first. You can get an access token using either + :class:`DropboxOAuth2Flow` or :class:`DropboxOAuth2FlowNoRedirect`. + + Args: + - ``oauth2_access_token``: An OAuth 2 access token (string). + - ``rest_client``: A :class:`dropbox.rest.RESTClient`-like object to use for making + requests. [optional] + + All of the API call methods can raise a :class:`dropbox.rest.ErrorResponse` exception if + the server returns a non-200 or invalid HTTP response. Note that a 401 + return status at any point indicates that the access token you're using + is no longer valid and the user must be put through the OAuth 2 + authorization flow again. + """ + + def __init__(self, oauth2_access_token, locale=None, rest_client=None): + if rest_client is None: rest_client = RESTClient + if isinstance(oauth2_access_token, basestring): + self.session = DropboxOAuth2Session(oauth2_access_token, locale) + elif isinstance(oauth2_access_token, DropboxSession): + # Backwards compatibility with OAuth 1 + if locale is not None: + raise ValueError("The 'locale' parameter to DropboxClient is only useful " + "when also passing in an OAuth 2 access token") + self.session = oauth2_access_token + else: + raise ValueError("'oauth2_access_token' must either be a string or a DropboxSession") + self.rest_client = rest_client + + def request(self, target, params=None, method='POST', content_server=False): + """ + An internal method that builds the url, headers, and params for a Dropbox API request. + It is exposed if you need to make API calls not implemented in this library or if you + need to debug requests. + + Args: + - ``target``: The target URL with leading slash (e.g. '/files') + - ``params``: A dictionary of parameters to add to the request + - ``method``: An HTTP method (e.g. 'GET' or 'POST') + - ``content_server``: A boolean indicating whether the request is to the + API content server, for example to fetch the contents of a file + rather than its metadata. + + Returns: + - A tuple of ``(url, params, headers)`` that should be used to make the request. + OAuth will be added as needed within these fields. + """ + assert method in ['GET','POST', 'PUT'], "Only 'GET', 'POST', and 'PUT' are allowed." + if params is None: + params = {} + + host = self.session.API_CONTENT_HOST if content_server else self.session.API_HOST + base = self.session.build_url(host, target) + headers, params = self.session.build_access_headers(method, base, params) + + if method in ('GET', 'PUT'): + url = self.session.build_url(host, target, params) + else: + url = self.session.build_url(host, target) + + return url, params, headers + + + def account_info(self): + """Retrieve information about the user's account. + + Returns: + - A dictionary containing account information. + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#account-info + """ + url, params, headers = self.request("/account/info", method='GET') + + return self.rest_client.GET(url, headers) + + def get_chunked_uploader(self, file_obj, length): + """Creates a ChunkedUploader to upload the given file-like object. + + Args: + - ``file_obj``: The file-like object which is the source of the data + being uploaded. + - ``length``: The number of bytes to upload. + + The expected use of this function is as follows: + + .. code-block:: python + + bigFile = open("data.txt", 'rb') + + uploader = myclient.get_chunked_uploader(bigFile, size) + print "uploading: ", size + while uploader.offset < size: + try: + upload = uploader.upload_chunked() + except rest.ErrorResponse, e: + # perform error handling and retry logic + uploader.finish('/bigFile.txt') + + The SDK leaves the error handling and retry logic to the developer + to implement, as the exact requirements will depend on the application + involved. + """ + return DropboxClient.ChunkedUploader(self, file_obj, length) + + class ChunkedUploader(object): + """Contains the logic around a chunked upload, which uploads a + large file to Dropbox via the /chunked_upload endpoint + """ + def __init__(self, client, file_obj, length): + self.client = client + self.offset = 0 + self.upload_id = None + + self.last_block = None + self.file_obj = file_obj + self.target_length = length + + + def upload_chunked(self, chunk_size = 4 * 1024 * 1024): + """Uploads data from this ChunkedUploader's file_obj in chunks, until + an error occurs. Throws an exception when an error occurs, and can + be called again to resume the upload. + + Args: + - ``chunk_size``: The number of bytes to put in each chunk. [default 4 MB] + """ + + while self.offset < self.target_length: + next_chunk_size = min(chunk_size, self.target_length - self.offset) + if self.last_block == None: + self.last_block = self.file_obj.read(next_chunk_size) + + try: + (self.offset, self.upload_id) = self.client.upload_chunk(StringIO(self.last_block), next_chunk_size, self.offset, self.upload_id) + self.last_block = None + except ErrorResponse, e: + reply = e.body + if "offset" in reply and reply['offset'] != 0: + if reply['offset'] > self.offset: + self.last_block = None + self.offset = reply['offset'] + + def finish(self, path, overwrite=False, parent_rev=None): + """Commits the bytes uploaded by this ChunkedUploader to a file + in the users dropbox. + + Args: + - ``path``: The full path of the file in the Dropbox. + - ``overwrite``: Whether to overwrite an existing file at the given path. [default False] + If overwrite is False and a file already exists there, Dropbox + will rename the upload to make sure it doesn't overwrite anything. + You need to check the metadata returned for the new name. + This field should only be True if your intent is to potentially + clobber changes to a file that you don't know about. + - ``parent_rev``: The rev field from the 'parent' of this upload. [optional] + If your intent is to update the file at the given path, you should + pass the parent_rev parameter set to the rev value from the most recent + metadata you have of the existing file at that path. If the server + has a more recent version of the file at the specified path, it will + automatically rename your uploaded file, spinning off a conflict. + Using this parameter effectively causes the overwrite parameter to be ignored. + The file will always be overwritten if you send the most-recent parent_rev, + and it will never be overwritten if you send a less-recent one. + """ + + path = "/commit_chunked_upload/%s%s" % (self.client.session.root, format_path(path)) + + params = dict( + overwrite = bool(overwrite), + upload_id = self.upload_id + ) + + if parent_rev is not None: + params['parent_rev'] = parent_rev + + url, params, headers = self.client.request(path, params, content_server=True) + + return self.client.rest_client.POST(url, params, headers) + + def upload_chunk(self, file_obj, length, offset=0, upload_id=None): + """Uploads a single chunk of data from the given file like object. The majority of users + should use the ChunkedUploader object, which provides a simpler interface to the + chunked_upload API endpoint. + + Args: + - ``file_obj``: The source of the data to upload + - ``length``: The number of bytes to upload in one chunk. + + Returns: + - The reply from the server, as a dictionary + """ + + params = dict() + + if upload_id: + params['upload_id'] = upload_id + params['offset'] = offset + + url, ignored_params, headers = self.request("/chunked_upload", params, method='PUT', content_server=True) + + try: + reply = self.rest_client.PUT(url, file_obj, headers) + return reply['offset'], reply['upload_id'] + except ErrorResponse, e: + raise e + + + def put_file(self, full_path, file_obj, overwrite=False, parent_rev=None): + """Upload a file. + + A typical use case would be as follows: + + .. code-block:: python + + f = open('working-draft.txt', 'rb') + response = client.put_file('/magnum-opus.txt', f) + print "uploaded:", response + + which would return the metadata of the uploaded file, similar to: + + .. code-block:: python + + { + 'bytes': 77, + 'icon': 'page_white_text', + 'is_dir': False, + 'mime_type': 'text/plain', + 'modified': 'Wed, 20 Jul 2011 22:04:50 +0000', + 'path': '/magnum-opus.txt', + 'rev': '362e2029684fe', + 'revision': 221922, + 'root': 'dropbox', + 'size': '77 bytes', + 'thumb_exists': False + } + + Args: + - ``full_path``: The full path to upload the file to, *including the file name*. + If the destination directory does not yet exist, it will be created. + - ``file_obj``: A file-like object to upload. If you would like, you can pass a string as file_obj. + - ``overwrite``: Whether to overwrite an existing file at the given path. [default False] + If overwrite is False and a file already exists there, Dropbox + will rename the upload to make sure it doesn't overwrite anything. + You need to check the metadata returned for the new name. + This field should only be True if your intent is to potentially + clobber changes to a file that you don't know about. + - ``parent_rev``: The rev field from the 'parent' of this upload. [optional] + If your intent is to update the file at the given path, you should + pass the parent_rev parameter set to the rev value from the most recent + metadata you have of the existing file at that path. If the server + has a more recent version of the file at the specified path, it will + automatically rename your uploaded file, spinning off a conflict. + Using this parameter effectively causes the overwrite parameter to be ignored. + The file will always be overwritten if you send the most-recent parent_rev, + and it will never be overwritten if you send a less-recent one. + + Returns: + - A dictionary containing the metadata of the newly uploaded file. + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#files-put + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 503: User over quota + + Note: In Python versions below version 2.6, httplib doesn't handle file-like objects. + In that case, this code will read the entire file into memory (!). + """ + path = "/files_put/%s%s" % (self.session.root, format_path(full_path)) + + params = { + 'overwrite': bool(overwrite), + } + + if parent_rev is not None: + params['parent_rev'] = parent_rev + + + url, params, headers = self.request(path, params, method='PUT', content_server=True) + + return self.rest_client.PUT(url, file_obj, headers) + + def get_file(self, from_path, rev=None): + """Download a file. + + Unlike most other calls, get_file returns a raw HTTPResponse with the connection open. + You should call .read() and perform any processing you need, then close the HTTPResponse. + + A typical usage looks like this: + + .. code-block:: python + + out = open('magnum-opus.txt', 'w') + f = client.get_file('/magnum-opus.txt').read() + out.write(f) + + which would download the file ``magnum-opus.txt`` and write the contents into + the file ``magnum-opus.txt`` on the local filesystem. + + Args: + - ``from_path``: The path to the file to be downloaded. + - ``rev``: A previous rev value of the file to be downloaded. [optional] + + Returns: + - An httplib.HTTPResponse that is the result of the request. + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No file was found at the given path, or the file that was there was deleted. + - 200: Request was okay but response was malformed in some way. + """ + path = "/files/%s%s" % (self.session.root, format_path(from_path)) + + params = {} + if rev is not None: + params['rev'] = rev + + url, params, headers = self.request(path, params, method='GET', content_server=True) + return self.rest_client.request("GET", url, headers=headers, raw_response=True) + + def get_file_and_metadata(self, from_path, rev=None): + """Download a file alongwith its metadata. + + Acts as a thin wrapper around get_file() (see :meth:`get_file()` comments for + more details) + + A typical usage looks like this: + + .. code-block:: python + + out = open('magnum-opus.txt', 'w') + f, metadata = client.get_file_and_metadata('/magnum-opus.txt') + out.write(f) + + Args: + - ``from_path``: The path to the file to be downloaded. + - ``rev``: A previous rev value of the file to be downloaded. [optional] + + Returns: + - An httplib.HTTPResponse that is the result of the request. + - A dictionary containing the metadata of the file (see + https://www.dropbox.com/developers/reference/api#metadata for details). + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No file was found at the given path, or the file that was there was deleted. + - 200: Request was okay but response was malformed in some way. + """ + file_res = self.get_file(from_path, rev) + metadata = DropboxClient.__parse_metadata_as_dict(file_res) + + return file_res, metadata + + @staticmethod + def __parse_metadata_as_dict(dropbox_raw_response): + """Parses file metadata from a raw dropbox HTTP response, raising a + :class:`dropbox.rest.ErrorResponse` if parsing fails. + """ + metadata = None + for header, header_val in dropbox_raw_response.getheaders(): + if header.lower() == 'x-dropbox-metadata': + try: + metadata = json.loads(header_val) + except ValueError: + raise ErrorResponse(dropbox_raw_response) + if not metadata: raise ErrorResponse(dropbox_raw_response) + return metadata + + def delta(self, cursor=None): + """A way of letting you keep up with changes to files and folders in a + user's Dropbox. You can periodically call delta() to get a list of "delta + entries", which are instructions on how to update your local state to + match the server's state. + + Args: + - ``cursor``: On the first call, omit this argument (or pass in ``None``). On + subsequent calls, pass in the ``cursor`` string returned by the previous + call. + + Returns: A dict with three fields. + - ``entries``: A list of "delta entries" (described below) + - ``reset``: If ``True``, you should your local state to be an empty folder + before processing the list of delta entries. This is only ``True`` only + in rare situations. + - ``cursor``: A string that is used to keep track of your current state. + On the next call to delta(), pass in this value to return entries + that were recorded since the cursor was returned. + - ``has_more``: If ``True``, then there are more entries available; you can + call delta() again immediately to retrieve those entries. If ``False``, + then wait at least 5 minutes (preferably longer) before checking again. + + Delta Entries: Each entry is a 2-item list of one of following forms: + - [*path*, *metadata*]: Indicates that there is a file/folder at the given + path. You should add the entry to your local path. (The *metadata* + value is the same as what would be returned by the ``metadata()`` call.) + + - If the new entry includes parent folders that don't yet exist in your + local state, create those parent folders in your local state. You + will eventually get entries for those parent folders. + - If the new entry is a file, replace whatever your local state has at + *path* with the new entry. + - If the new entry is a folder, check what your local state has at + *path*. If it's a file, replace it with the new entry. If it's a + folder, apply the new *metadata* to the folder, but do not modify + the folder's children. + - [*path*, ``nil``]: Indicates that there is no file/folder at the *path* on + Dropbox. To update your local state to match, delete whatever is at *path*, + including any children (you will sometimes also get "delete" delta entries + for the children, but this is not guaranteed). If your local state doesn't + have anything at *path*, ignore this entry. + + Remember: Dropbox treats file names in a case-insensitive but case-preserving + way. To facilitate this, the *path* strings above are lower-cased versions of + the actual path. The *metadata* dicts have the original, case-preserved path. + """ + path = "/delta" + + params = {} + if cursor is not None: + params['cursor'] = cursor + + url, params, headers = self.request(path, params) + + return self.rest_client.POST(url, params, headers) + + + def create_copy_ref(self, from_path): + """Creates and returns a copy ref for a specific file. The copy ref can be + used to instantly copy that file to the Dropbox of another account. + + Args: + - ``path``: The path to the file for a copy ref to be created on. + + Returns: + - A dictionary that looks like the following example: + + ``{"expires":"Fri, 31 Jan 2042 21:01:05 +0000", "copy_ref":"z1X6ATl6aWtzOGq0c3g5Ng"}`` + + """ + path = "/copy_ref/%s%s" % (self.session.root, format_path(from_path)) + + url, params, headers = self.request(path, {}, method='GET') + + return self.rest_client.GET(url, headers) + + def add_copy_ref(self, copy_ref, to_path): + """Adds the file referenced by the copy ref to the specified path + + Args: + - ``copy_ref``: A copy ref string that was returned from a create_copy_ref call. + The copy_ref can be created from any other Dropbox account, or from the same account. + - ``path``: The path to where the file will be created. + + Returns: + - A dictionary containing the metadata of the new copy of the file. + """ + path = "/fileops/copy" + + params = {'from_copy_ref': copy_ref, + 'to_path': format_path(to_path), + 'root': self.session.root} + + url, params, headers = self.request(path, params) + + return self.rest_client.POST(url, params, headers) + + def file_copy(self, from_path, to_path): + """Copy a file or folder to a new location. + + Args: + - ``from_path``: The path to the file or folder to be copied. + - ``to_path``: The destination path of the file or folder to be copied. + This parameter should include the destination filename (e.g. + from_path: '/test.txt', to_path: '/dir/test.txt'). If there's + already a file at the to_path it will raise an ErrorResponse. + + + Returns: + - A dictionary containing the metadata of the new copy of the file or folder. + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#fileops-copy + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of: + + - 400: Bad request (may be due to many things; check e.error for details) + - 403: An invalid move operation was attempted (e.g. there is already a file at the given destination, or moving a shared folder into a shared folder). + - 404: No file was found at given from_path. + - 503: User over storage quota. + """ + params = {'root': self.session.root, + 'from_path': format_path(from_path), + 'to_path': format_path(to_path), + } + + url, params, headers = self.request("/fileops/copy", params) + + return self.rest_client.POST(url, params, headers) + + + def file_create_folder(self, path): + """Create a folder. + + Args: + - ``path``: The path of the new folder. + + Returns: + - A dictionary containing the metadata of the newly created folder. + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#fileops-create-folder + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 403: A folder at that path already exists. + """ + params = {'root': self.session.root, 'path': format_path(path)} + + url, params, headers = self.request("/fileops/create_folder", params) + + return self.rest_client.POST(url, params, headers) + + + def file_delete(self, path): + """Delete a file or folder. + + Args: + - ``path``: The path of the file or folder. + + Returns: + - A dictionary containing the metadata of the just deleted file. + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#fileops-delete + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No file was found at the given path. + """ + params = {'root': self.session.root, 'path': format_path(path)} + + url, params, headers = self.request("/fileops/delete", params) + + return self.rest_client.POST(url, params, headers) + + + def file_move(self, from_path, to_path): + """Move a file or folder to a new location. + + Args: + - ``from_path``: The path to the file or folder to be moved. + - ``to_path``: The destination path of the file or folder to be moved. + This parameter should include the destination filename (e.g. if + ``from_path`` is ``'/test.txt'``, ``to_path`` might be + ``'/dir/test.txt'``). If there's already a file at the + ``to_path``, this file or folder will be renamed to be unique. + + Returns: + - A dictionary containing the metadata of the new copy of the file or folder. + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#fileops-move + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No file was found at given from_path. + - 503: User over storage quota. + """ + params = {'root': self.session.root, 'from_path': format_path(from_path), 'to_path': format_path(to_path)} + + url, params, headers = self.request("/fileops/move", params) + + return self.rest_client.POST(url, params, headers) + + + def metadata(self, path, list=True, file_limit=25000, hash=None, rev=None, include_deleted=False): + """Retrieve metadata for a file or folder. + + A typical use would be: + + .. code-block:: python + + folder_metadata = client.metadata('/') + print "metadata:", folder_metadata + + which would return the metadata of the root directory. This + will look something like: + + .. code-block:: python + + { + 'bytes': 0, + 'contents': [ + { + 'bytes': 0, + 'icon': 'folder', + 'is_dir': True, + 'modified': 'Thu, 25 Aug 2011 00:03:15 +0000', + 'path': '/Sample Folder', + 'rev': '803beb471', + 'revision': 8, + 'root': 'dropbox', + 'size': '0 bytes', + 'thumb_exists': False + }, + { + 'bytes': 77, + 'icon': 'page_white_text', + 'is_dir': False, + 'mime_type': 'text/plain', + 'modified': 'Wed, 20 Jul 2011 22:04:50 +0000', + 'path': '/magnum-opus.txt', + 'rev': '362e2029684fe', + 'revision': 221922, + 'root': 'dropbox', + 'size': '77 bytes', + 'thumb_exists': False + } + ], + 'hash': 'efdac89c4da886a9cece1927e6c22977', + 'icon': 'folder', + 'is_dir': True, + 'path': '/', + 'root': 'app_folder', + 'size': '0 bytes', + 'thumb_exists': False + } + + In this example, the root directory contains two things: ``Sample Folder``, + which is a folder, and ``/magnum-opus.txt``, which is a text file 77 bytes long + + Args: + - ``path``: The path to the file or folder. + - ``list``: Whether to list all contained files (only applies when + path refers to a folder). + - ``file_limit``: The maximum number of file entries to return within + a folder. If the number of files in the directory exceeds this + limit, an exception is raised. The server will return at max + 25,000 files within a folder. + - ``hash``: Every directory listing has a hash parameter attached that + can then be passed back into this function later to save on + bandwidth. Rather than returning an unchanged folder's contents, + the server will instead return a 304. + - ``rev``: The revision of the file to retrieve the metadata for. [optional] + This parameter only applies for files. If omitted, you'll receive + the most recent revision metadata. + + Returns: + - A dictionary containing the metadata of the file or folder + (and contained files if appropriate). + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#metadata + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 304: Current directory hash matches hash parameters, so contents are unchanged. + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No file was found at given path. + - 406: Too many file entries to return. + """ + path = "/metadata/%s%s" % (self.session.root, format_path(path)) + + params = {'file_limit': file_limit, + 'list': 'true', + 'include_deleted': include_deleted, + } + + if not list: + params['list'] = 'false' + if hash is not None: + params['hash'] = hash + if rev: + params['rev'] = rev + + url, params, headers = self.request(path, params, method='GET') + + return self.rest_client.GET(url, headers) + + def thumbnail(self, from_path, size='large', format='JPEG'): + """Download a thumbnail for an image. + + Unlike most other calls, thumbnail returns a raw HTTPResponse with the connection open. + You should call .read() and perform any processing you need, then close the HTTPResponse. + + Args: + - ``from_path``: The path to the file to be thumbnailed. + - ``size``: A string describing the desired thumbnail size. + At this time, 'small', 'medium', and 'large' are + officially supported sizes (32x32, 64x64, and 128x128 + respectively), though others may be available. Check + https://www.dropbox.com/developers/reference/api#thumbnails for + more details. + + Returns: + - An httplib.HTTPResponse that is the result of the request. + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No file was found at the given from_path, or files of that type cannot be thumbnailed. + - 415: Image is invalid and cannot be thumbnailed. + """ + assert format in ['JPEG', 'PNG'], "expected a thumbnail format of 'JPEG' or 'PNG', got %s" % format + + path = "/thumbnails/%s%s" % (self.session.root, format_path(from_path)) + + url, params, headers = self.request(path, {'size': size, 'format': format}, method='GET', content_server=True) + return self.rest_client.request("GET", url, headers=headers, raw_response=True) + + def thumbnail_and_metadata(self, from_path, size='large', format='JPEG'): + """Download a thumbnail for an image alongwith its metadata. + + Acts as a thin wrapper around thumbnail() (see :meth:`thumbnail()` comments for + more details) + + Args: + - ``from_path``: The path to the file to be thumbnailed. + - ``size``: A string describing the desired thumbnail size. See :meth:`thumbnail()` + for details. + + Returns: + - An httplib.HTTPResponse that is the result of the request. + - A dictionary containing the metadata of the file whose thumbnail + was downloaded (see https://www.dropbox.com/developers/reference/api#metadata + for details). + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No file was found at the given from_path, or files of that type cannot be thumbnailed. + - 415: Image is invalid and cannot be thumbnailed. + - 200: Request was okay but response was malformed in some way. + """ + thumbnail_res = self.thumbnail(from_path, size, format) + metadata = DropboxClient.__parse_metadata_as_dict(thumbnail_res) + + return thumbnail_res, metadata + + def search(self, path, query, file_limit=1000, include_deleted=False): + """Search directory for filenames matching query. + + Args: + - ``path``: The directory to search within. + - ``query``: The query to search on (minimum 3 characters). + - ``file_limit``: The maximum number of file entries to return within a folder. + The server will return at max 1,000 files. + - ``include_deleted``: Whether to include deleted files in search results. + + Returns: + - A list of the metadata of all matching files (up to + file_limit entries). For a detailed description of what + this call returns, visit: + https://www.dropbox.com/developers/reference/api#search + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + """ + path = "/search/%s%s" % (self.session.root, format_path(path)) + + params = { + 'query': query, + 'file_limit': file_limit, + 'include_deleted': include_deleted, + } + + url, params, headers = self.request(path, params) + + return self.rest_client.POST(url, params, headers) + + def revisions(self, path, rev_limit=1000): + """Retrieve revisions of a file. + + Args: + - ``path``: The file to fetch revisions for. Note that revisions + are not available for folders. + - ``rev_limit``: The maximum number of file entries to return within + a folder. The server will return at max 1,000 revisions. + + Returns: + - A list of the metadata of all matching files (up to rev_limit entries). + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#revisions + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: No revisions were found at the given path. + """ + path = "/revisions/%s%s" % (self.session.root, format_path(path)) + + params = { + 'rev_limit': rev_limit, + } + + url, params, headers = self.request(path, params, method='GET') + + return self.rest_client.GET(url, headers) + + def restore(self, path, rev): + """Restore a file to a previous revision. + + Args: + - ``path``: The file to restore. Note that folders can't be restored. + - ``rev``: A previous rev value of the file to be restored to. + + Returns: + - A dictionary containing the metadata of the newly restored file. + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#restore + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: Unable to find the file at the given revision. + """ + path = "/restore/%s%s" % (self.session.root, format_path(path)) + + params = { + 'rev': rev, + } + + url, params, headers = self.request(path, params) + + return self.rest_client.POST(url, params, headers) + + def media(self, path): + """Get a temporary unauthenticated URL for a media file. + + All of Dropbox's API methods require OAuth, which may cause problems in + situations where an application expects to be able to hit a URL multiple times + (for example, a media player seeking around a video file). This method + creates a time-limited URL that can be accessed without any authentication, + and returns that to you, along with an expiration time. + + Args: + - ``path``: The file to return a URL for. Folders are not supported. + + Returns: + - A dictionary that looks like the following example: + + ``{'url': 'https://dl.dropbox.com/0/view/wvxv1fw6on24qw7/file.mov', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}`` + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#media + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: Unable to find the file at the given path. + """ + path = "/media/%s%s" % (self.session.root, format_path(path)) + + url, params, headers = self.request(path, method='GET') + + return self.rest_client.GET(url, headers) + + def share(self, path, short_url=True): + """Create a shareable link to a file or folder. + + Shareable links created on Dropbox are time-limited, but don't require any + authentication, so they can be given out freely. The time limit should allow + at least a day of shareability, though users have the ability to disable + a link from their account if they like. + + Args: + - ``path``: The file or folder to share. + + Returns: + - A dictionary that looks like the following example: + + ``{'url': 'http://www.dropbox.com/s/m/a2mbDa2', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}`` + + For a detailed description of what this call returns, visit: + https://www.dropbox.com/developers/reference/api#shares + + Raises: + - A :class:`dropbox.rest.ErrorResponse` with an HTTP status of + + - 400: Bad request (may be due to many things; check e.error for details) + - 404: Unable to find the file at the given path. + """ + path = "/shares/%s%s" % (self.session.root, format_path(path)) + + params = { + 'short_url': short_url, + } + + url, params, headers = self.request(path, params, method='GET') + + return self.rest_client.GET(url, headers) + +class DropboxOAuth2FlowBase(object): + + def __init__(self, consumer_key, consumer_secret, locale=None, rest_client=RESTClient): + self.consumer_key = consumer_key + self.consumer_secret = consumer_secret + self.locale = locale + self.rest_client = rest_client + + def _get_authorize_url(self, redirect_uri, state): + params = dict(response_type='code', + client_id=self.consumer_key) + if redirect_uri is not None: + params['redirect_uri'] = redirect_uri + if state is not None: + params['state'] = state + + return self.build_url(BaseSession.WEB_HOST, '/oauth2/authorize', params) + + def _finish(self, code, redirect_uri): + url = self.build_url(BaseSession.API_HOST, '/oauth2/token') + params = {'grant_type': 'authorization_code', + 'code': code, + 'client_id': self.consumer_key, + 'client_secret': self.consumer_secret, + } + if self.locale is not None: + params['locale'] = self.locale + if redirect_uri is not None: + params['redirect_uri'] = redirect_uri + + response = self.rest_client.POST(url, params=params) + access_token = response["access_token"] + user_id = response["uid"] + return access_token, user_id + + def build_path(self, target, params=None): + """Build the path component for an API URL. + + This method urlencodes the parameters, adds them + to the end of the target url, and puts a marker for the API + version in front. + + Args: + - ``target``: A target url (e.g. '/files') to build upon. + - ``params``: A dictionary of parameters (name to value). [optional] + + Returns: + - The path and parameters components of an API URL. + """ + if sys.version_info < (3,) and type(target) == unicode: + target = target.encode("utf8") + + target_path = urllib.quote(target) + + params = params or {} + params = params.copy() + + if self.locale: + params['locale'] = self.locale + + if params: + return "/%d%s?%s" % (BaseSession.API_VERSION, target_path, urllib.urlencode(params)) + else: + return "/%d%s" % (BaseSession.API_VERSION, target_path) + + def build_url(self, host, target, params=None): + """Build an API URL. + + This method adds scheme and hostname to the path + returned from build_path. + + Args: + - ``target``: A target url (e.g. '/files') to build upon. + - ``params``: A dictionary of parameters (name to value). [optional] + + Returns: + - The full API URL. + """ + return "https://%s%s" % (host, self.build_path(target, params)) + +class DropboxOAuth2FlowNoRedirect(DropboxOAuth2FlowBase): + """ + OAuth 2 authorization helper for apps that can't provide a redirect URI + (such as the command-line example apps). + + Args: + - ``consumer_key``: Your API app's "app key" + - ``consumer_secret``: Your API app's "app secret" + + .. code-block:: python + + from dropbox.client import DropboxOAuth2FlowNoRedirect, DropboxClient + from dropbox import rest as dbrest + + oauth_flow = DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET) + + authorize_url = oauth_flow.start() + print "1. Go to: " + authorize_url + print "2. Click \\"Allow\\" (you might have to log in first)." + print "3. Copy the authorization code." + auth_code = raw_input("Enter the authorization code here: ").strip() + + try: + access_token, user_id = auth_flow.finish(auth_code) + except dbrest.ErrorResponse, e: + print('Error: %s' % (e,)) + return + + c = DropboxClient(access_token) + """ + + def __init__(self, consumer_key, consumer_secret, locale=None, rest_client=None): + if rest_client is None: rest_client = RESTClient + super(DropboxOAuth2FlowNoRedirect, self).__init__(consumer_key, consumer_secret, locale, rest_client) + + def start(self): + """ + Returns the URL for a page on Dropbox's website. This page will let the user "approve" + your app, which gives your app permission to access the user's Dropbox account. + Tell the user to visit this URL and approve your app. + """ + return self._get_authorize_url(None, None) + + def finish(self, code): + """ + If the user approves your app, they will be presented with an "authorization code". Have + the user copy/paste that authorization code into your app and then call this method to + get an access token. + + Args: + - ``code``: The authorization code shown to the user when they approved your app. + + Returns a pair of ``(access_token, user_id)``. ``access_token`` is a string that + can be passed to DropboxClient. ``user_id`` is the Dropbox user ID (string) of the user that + just approved your app. + """ + return self._finish(code, None) + +class DropboxOAuth2Flow(DropboxOAuth2FlowBase): + """ + OAuth 2 authorization helper. Use this for web apps. + + OAuth 2 has a two-step authorization process. The first step is having the user authorize + your app. The second involves getting an OAuth 2 access token from Dropbox. + + Args: + - ``consumer_key``: Your API app's "app key". + - ``consumer_secret``: Your API app's "app secret". + - ``redirect_uri``: The URI that the Dropbox server will redirect the user to after the user + finishes authorizing your app. This URI must be HTTPS-based and pre-registered with + the Dropbox servers, though localhost URIs are allowed without pre-registration and can + be either HTTP or HTTPS. + - ``session``: A dict-like object that represents the current user's web session (will be + used to save the CSRF token). + - ``csrf_token_key``: The key to use when storing the CSRF token in the session (for + example: "dropbox-auth-csrf-token"). + + .. code-block:: python + + from dropbox.client import DropboxOAuth2Flow, DropboxClient + + def get_dropbox_auth_flow(web_app_session): + redirect_uri = "https://my-web-server.org/dropbox-auth-finish") + return DropboxOAuth2Flow(APP_KEY, APP_SECRET, redirect_uri, + web_app_session, "dropbox-auth-csrf-token") + + # URL handler for /dropbox-auth-start + def dropbox_auth_start(web_app_session, request): + authorize_url = get_dropbox_auth_flow(web_app_session).start() + redirect_to(authorize_url) + + # URL handler for /dropbox-auth-finish + def dropbox_auth_finish(web_app_session, request): + try: + access_token, user_id, url_state = \\ + get_dropbox_auth_flow(web_app_session).finish(request.query_params) + except DropboxOAuth2Flow.BadRequestException, e: + http_status(400) + except DropboxOAuth2Flow.BadStateException, e: + # Start the auth flow again. + redirect_to("/dropbox-auth-start") + except DropboxOAuth2Flow.CsrfException, e: + http_status(403) + except DropboxOAuth2Flow.NotApprovedException, e: + flash('Not approved? Why not, bro?') + return redirect_to("/home") + except DropboxOAuth2Flow.ProviderException, e: + logger.log("Auth error: %s" % (e,)) + http_status(403) + + """ + + def __init__(self, consumer_key, consumer_secret, redirect_uri, session, csrf_token_session_key, + locale=None, rest_client=None): + if rest_client is None: rest_client = RESTClient + super(DropboxOAuth2Flow, self).__init__(consumer_key, consumer_secret, locale, rest_client) + self.redirect_uri = redirect_uri + self.session = session + self.csrf_token_session_key = csrf_token_session_key + + def start(self, url_state=None): + """ + Starts the OAuth 2 authorization process. + + This function builds an "authorization URL". You should redirect your user's browser to + this URL, which will give them an opportunity to grant your app access to their Dropbox + account. When the user completes this process, they will be automatically redirected to + the ``redirect_uri`` you passed in to the constructor. + + This function will also save a CSRF token to ``session[csrf_token_session_key]`` (as + provided to the constructor). This CSRF token will be checked on :meth:`finish()` to + prevent request forgery. + + Args: + - ``url_state``: Any data that you would like to keep in the URL through the + authorization process. This exact value will be returned to you by :meth:`finish()`. + + Returns: + - + """ + csrf_token = base64.urlsafe_b64encode(os.urandom(16)) + state = csrf_token + if url_state is not None: + state += "|" + url_state + self.session[self.csrf_token_session_key] = csrf_token + + return self._get_authorize_url(self.redirect_uri, state) + + def finish(self, query_params): + """ + Call this after the user has visited the authorize URL (see :meth:`start()`), approved your + app and was redirected to your redirect URI. + + - ``query_params``: The query parameters on the GET request to your redirect URI. + + Returns a tuple of ``(access_token, user_id, url_state)``. ``access_token`` can be used to + construct a :class:`DropboxClient`. ``user_id`` is the Dropbox user ID (string) of the user + that just approved your app. ``url_state`` is the value you originally passed in to + :meth:`start()`. + + Can throw + :class:`BadRequestException`, + :class:`BadStateException`, + :class:`CsrfException`, + :class:`NotApprovedException`, + :class:`ProviderException`. + """ + csrf_token_from_session = self.session[self.csrf_token_session_key] + + # Check well-formedness of request. + + state = query_params.get('state') + if state is None: + raise self.BadRequestException("Missing query parameter 'state'.") + + error = query_params.get('error') + error_description = query_params.get('error_description') + code = query_params.get('code') + + if error is not None and code is not None: + raise self.BadRequestException("Query parameters 'code' and 'error' are both set; " + " only one must be set.") + if error is None and code is None: + raise self.BadRequestException("Neither query parameter 'code' or 'error' is set.") + + # Check CSRF token + + if csrf_token_from_session is None: + raise self.BadStateError("Missing CSRF token in session.") + if len(csrf_token_from_session) <= 20: + raise AssertionError("CSRF token unexpectedly short: %r" % (csrf_token_from_session,)) + + split_pos = state.find('|') + if split_pos < 0: + given_csrf_token = state + url_state = None + else: + given_csrf_token = state[0:split_pos] + url_state = state[split_pos+1:] + + if not _safe_equals(csrf_token_from_session, given_csrf_token): + raise self.CsrfException("expected %r, got %r" % (csrf_token_from_session, given_csrf_token)) + + del self.session[self.csrf_token_session_key] + + # Check for error identifier + + if error is not None: + if error == 'access_denied': + # The user clicked "Deny" + if error_description is None: + raise self.NotApprovedException("No additional description from Dropbox") + else: + raise self.NotApprovedException("Additional description from Dropbox: " + error_description) + else: + # All other errors + full_message = error + if error_description is not None: + full_message += ": " + error_description + raise self.ProviderError(full_message) + + # If everything went ok, make the network call to get an access token. + + access_token, user_id = self._finish(code, self.redirect_uri) + return access_token, user_id, url_state + + class BadRequestException(Exception): + """ + Thrown if the redirect URL was missing parameters or if the given parameters were not valid. + + The recommended action is to show an HTTP 400 error page. + """ + pass + + class BadStateException(Exception): + """ + Thrown if all the parameters are correct, but there's no CSRF token in the session. This + probably means that the session expired. + + The recommended action is to redirect the user's browser to try the approval process again. + """ + pass + + class CsrfException(Exception): + """ + Thrown if the given 'state' parameter doesn't contain the CSRF token from the user's session. + This is blocked to prevent CSRF attacks. + + The recommended action is to respond with an HTTP 403 error page. + """ + pass + + class NotApprovedException(Exception): + """ + The user chose not to approve your app. + """ + pass + + class ProviderException(Exception): + """ + Dropbox redirected to your redirect URI with some unexpected error identifier and error + message. + + The recommended action is to log the error, tell the user something went wrong, and let + them try again. + """ + pass + +def _safe_equals(a, b): + if len(a) != len(b): return False + res = 0 + for ca, cb in zip(a, b): + res |= ord(ca) ^ ord(cb) + return res == 0 diff -Nru gtkrawgallery-0.9.8/src/dropbox/rest.py gtkrawgallery-0.9.9/src/dropbox/rest.py --- gtkrawgallery-0.9.8/src/dropbox/rest.py 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/rest.py 2013-09-24 08:49:58.000000000 +0000 @@ -0,0 +1,327 @@ +""" +A simple JSON REST request abstraction layer that is used by the +dropbox.client and dropbox.session modules. You shouldn't need to use this. +""" + +import httplib +#import pkg_resources +import re +import socket +import ssl +import sys +import urllib +import urlparse +from . import util + +try: + import json +except ImportError: + import simplejson as json + +SDK_VERSION = "1.6" + +#TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt') + +import os.path +import os + +if os.path.isdir('/usr/local/share/gtkrawgallery'): + gtkrg_datadir = '/usr/local/share/gtkrawgallery' +elif os.path.isdir('/usr/share/gtkrawgallery'): + gtkrg_datadir = '/usr/share/gtkrawgallery' +elif sys.platform.startswith('win'): + gtkrg_datadir = os.path.join(os.getcwd(), 'src') +TRUSTED_CERT_FILE = os.path.abspath(os.path.join(gtkrg_datadir, 'dropbox/trusted-certs.crt')) + +class ProperHTTPSConnection(httplib.HTTPConnection): + """ + httplib.HTTPSConnection is broken because it doesn't do server certificate + validation. This class does certificate validation by ensuring: + 1. The certificate sent down by the server has a signature chain to one of + the certs in our 'trusted-certs.crt' (this is mostly handled by the 'ssl' + module). + 2. The hostname in the certificate matches the hostname we're connecting to. + """ + + def __init__(self, host, port, trusted_cert_file=TRUSTED_CERT_FILE): + httplib.HTTPConnection.__init__(self, host, port) + self.ca_certs = trusted_cert_file + self.cert_reqs = ssl.CERT_REQUIRED + + def connect(self): + sock = create_connection((self.host, self.port)) + self.sock = ssl.wrap_socket(sock, cert_reqs=self.cert_reqs, ca_certs=self.ca_certs) + cert = self.sock.getpeercert() + hostname = self.host.split(':', 0)[0] + match_hostname(cert, hostname) + +class CertificateError(ValueError): + pass + +def _dnsname_to_pat(dn): + pats = [] + for frag in dn.split(r'.'): + if frag == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + else: + # Otherwise, '*' matches any dotless fragment. + frag = re.escape(frag) + pats.append(frag.replace(r'\*', '[^.]*')) + return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + +# This was ripped from Python 3.2 so it's not tested +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules + are mostly followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_to_pat(value).match(hostname): + return + dnsnames.append(value) + if not san: + # The subject is only checked when subjectAltName is empty + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_to_pat(value).match(hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r doesn't match either of %s" % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or subjectAltName fields were found") + +def create_connection(address): + host, port = address + err = None + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + sock.connect(sa) + return sock + + except socket.error, _: + err = _ + if sock is not None: + sock.close() + + if err is not None: + raise err + else: + raise socket.error("getaddrinfo returns an empty list") + +def json_loadb(data): + if sys.version_info >= (3,): + data = data.decode('utf8') + return json.loads(data) + +class RESTClientObject(object): + def __init__(self, http_connect=None): + self.http_connect = http_connect + + def request(self, method, url, post_params=None, body=None, headers=None, raw_response=False): + post_params = post_params or {} + headers = headers or {} + headers['User-Agent'] = 'OfficialDropboxPythonSDK/' + SDK_VERSION + + if post_params: + if body: + raise ValueError("body parameter cannot be used with post_params parameter") + body = urllib.urlencode(post_params) + headers["Content-type"] = "application/x-www-form-urlencoded" + + # maintain dynamic lookup of ProperHTTPConnection + http_connect = self.http_connect + if http_connect is None: + http_connect = ProperHTTPSConnection + + host = urlparse.urlparse(url).hostname + conn = http_connect(host, 443) + + try: + # This code is here because httplib in pre-2.6 Pythons + # doesn't handle file-like objects as HTTP bodies and + # thus requires manual buffering + if not hasattr(body, 'read'): + conn.request(method, url, body, headers) + else: + # Content-Length should be set to prevent upload truncation errors. + clen, raw_data = util.analyze_file_obj(body) + headers["Content-Length"] = str(clen) + conn.request(method, url, "", headers) + if raw_data is not None: + conn.send(raw_data) + else: + BLOCKSIZE = 4 * 1024 * 1024 # 4MB buffering just because + bytes_read = 0 + while True: + data = body.read(BLOCKSIZE) + if not data: + break + # Catch Content-Length overflow before the HTTP server does + bytes_read += len(data) + if bytes_read > clen: + raise util.AnalyzeFileObjBug(clen, bytes_read) + conn.send(data) + if bytes_read != clen: + raise util.AnalyzeFileObjBug(clen, bytes_read) + + except socket.error, e: + raise RESTSocketError(host, e) + except CertificateError, e: + raise RESTSocketError(host, "SSL certificate error: %s" % e) + + r = conn.getresponse() + if r.status != 200: + raise ErrorResponse(r) + + if raw_response: + return r + else: + try: + resp = json_loadb(r.read()) + except ValueError: + raise ErrorResponse(r) + finally: + conn.close() + + return resp + + def GET(self, url, headers=None, raw_response=False): + assert type(raw_response) == bool + return self.request("GET", url, headers=headers, raw_response=raw_response) + + def POST(self, url, params=None, headers=None, raw_response=False): + assert type(raw_response) == bool + if params is None: + params = {} + + return self.request("POST", url, + post_params=params, headers=headers, raw_response=raw_response) + + def PUT(self, url, body, headers=None, raw_response=False): + assert type(raw_response) == bool + return self.request("PUT", url, body=body, headers=headers, raw_response=raw_response) + +class RESTClient(object): + IMPL = RESTClientObject() + + """ + An class with all static methods to perform JSON REST requests that is used internally + by the Dropbox Client API. It provides just enough gear to make requests + and get responses as JSON data (when applicable). All requests happen over SSL. + """ + + @classmethod + def request(cls, *n, **kw): + """Perform a REST request and parse the response. + + Args: + - ``method``: An HTTP method (e.g. 'GET' or 'POST'). + - ``url``: The URL to make a request to. + - ``post_params``: A dictionary of parameters to put in the body of the request. + This option may not be used if the body parameter is given. + - ``body``: The body of the request. Typically, this value will be a string. + It may also be a file-like object in Python 2.6 and above. The body + parameter may not be used with the post_params parameter. + - ``headers``: A dictionary of headers to send with the request. + - ``raw_response``: Whether to return the raw httplib.HTTPReponse object. [default False] + It's best enabled for requests that return large amounts of data that you + would want to .read() incrementally rather than loading into memory. Also + use this for calls where you need to read metadata like status or headers, + or if the body is not JSON. + + Returns: + - The JSON-decoded data from the server, unless raw_response is + specified, in which case an httplib.HTTPReponse object is returned instead. + + Raises: + - dropbox.rest.ErrorResponse: The returned HTTP status is not 200, or the body was + not parsed from JSON successfully. + - dropbox.rest.RESTSocketError: A socket.error was raised while contacting Dropbox. + """ + return cls.IMPL.request(*n, **kw) + + @classmethod + def GET(cls, *n, **kw): + """Perform a GET request using RESTClient.request""" + return cls.IMPL.GET(*n, **kw) + + @classmethod + def POST(cls, *n, **kw): + """Perform a POST request using RESTClient.request""" + return cls.IMPL.POST(*n, **kw) + + @classmethod + def PUT(cls, *n, **kw): + """Perform a PUT request using RESTClient.request""" + return cls.IMPL.PUT(*n, **kw) + +class RESTSocketError(socket.error): + """ + A light wrapper for socket.errors raised by dropbox.rest.RESTClient.request + that adds more information to the socket.error. + """ + + def __init__(self, host, e): + msg = "Error connecting to \"%s\": %s" % (host, str(e)) + socket.error.__init__(self, msg) + +class ErrorResponse(Exception): + """ + Raised by dropbox.rest.RESTClient.request for requests that: + - Return a non-200 HTTP response, or + - Have a non-JSON response body, or + - Have a malformed/missing header in the response. + + Most errors that Dropbox returns will have a error field that is unpacked and + placed on the ErrorResponse exception. In some situations, a user_error field + will also come back. Messages under user_error are worth showing to an end-user + of your app, while other errors are likely only useful for you as the developer. + """ + + def __init__(self, http_resp): + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.read() + self.headers = http_resp.getheaders() + + try: + self.body = json_loadb(self.body) + self.error_msg = self.body.get('error') + self.user_error_msg = self.body.get('user_error') + except ValueError: + self.error_msg = None + self.user_error_msg = None + + def __str__(self): + if self.user_error_msg and self.user_error_msg != self.error_msg: + # one is translated and the other is English + msg = "%s (%s)" % (self.user_error_msg, self.error_msg) + elif self.error_msg: + msg = self.error_msg + elif not self.body: + msg = self.reason + else: + msg = "Error parsing response body or headers: " +\ + "Body - %s Headers - %s" % (self.body, self.headers) + + return "[%d] %s" % (self.status, repr(msg)) + diff -Nru gtkrawgallery-0.9.8/src/dropbox/session.py gtkrawgallery-0.9.9/src/dropbox/session.py --- gtkrawgallery-0.9.8/src/dropbox/session.py 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/session.py 2013-09-24 08:49:58.000000000 +0000 @@ -0,0 +1,307 @@ +""" +dropbox.session.DropboxSession is responsible for holding OAuth authentication +info (app key/secret, request key/secret, access key/secret). It knows how to +use all of this information to craft properly constructed requests to Dropbox. + +A DropboxSession object must be passed to a dropbox.client.DropboxClient object upon +initialization. + +""" +from __future__ import absolute_import + +import random +import sys +import time +import urllib + +try: + from urlparse import parse_qs +except ImportError: + # fall back for Python 2.5 + from cgi import parse_qs + +from . import rest + +class OAuthToken(object): + """ + A class representing an OAuth token. Contains two fields: ``key`` and + ``secret``. + """ + def __init__(self, key, secret): + self.key = key + self.secret = secret + +class BaseSession(object): + API_VERSION = 1 + + API_HOST = "api.dropbox.com" + WEB_HOST = "www.dropbox.com" + API_CONTENT_HOST = "api-content.dropbox.com" + + def __init__(self, consumer_key, consumer_secret, access_type="auto", locale=None, rest_client=rest.RESTClient): + """Initialize a DropboxSession object. + + Your consumer key and secret are available + at https://www.dropbox.com/developers/apps + + Args: + + - ``access_type``: Either 'auto' (the default), 'dropbox', or + 'app_folder'. You probably don't need to specify this and should + just use the default. + - ``locale``: A locale string ('en', 'pt_PT', etc.) [optional] + The locale setting will be used to translate any user-facing error + messages that the server generates. At this time Dropbox supports + 'en', 'es', 'fr', 'de', and 'ja', though we will be supporting more + languages in the future. If you send a language the server doesn't + support, messages will remain in English. Look for these translated + messages in rest.ErrorResponse exceptions as e.user_error_msg. + + """ + assert access_type in ['dropbox', 'app_folder', 'auto'], "expected access_type of 'dropbox' or 'app_folder'" + self.consumer_creds = OAuthToken(consumer_key, consumer_secret) + self.token = None + self.request_token = None + self.root = 'sandbox' if access_type == 'app_folder' else access_type + self.locale = locale + self.rest_client = rest_client + + def is_linked(self): + """Return whether the DropboxSession has an access token attached.""" + return bool(self.token) + + def unlink(self): + """Remove any attached access token from the DropboxSession.""" + self.token = None + + def build_path(self, target, params=None): + """Build the path component for an API URL. + + This method urlencodes the parameters, adds them + to the end of the target url, and puts a marker for the API + version in front. + + Args: + - ``target``: A target url (e.g. '/files') to build upon. + - ``params``: A dictionary of parameters (name to value). [optional] + + Returns: + - The path and parameters components of an API URL. + """ + if sys.version_info < (3,) and type(target) == unicode: + target = target.encode("utf8") + + target_path = urllib.quote(target) + + params = params or {} + params = params.copy() + + if self.locale: + params['locale'] = self.locale + + if params: + return "/%d%s?%s" % (self.API_VERSION, target_path, urllib.urlencode(params)) + else: + return "/%d%s" % (self.API_VERSION, target_path) + + def build_url(self, host, target, params=None): + """Build an API URL. + + This method adds scheme and hostname to the path + returned from build_path. + + Args: + - ``target``: A target url (e.g. '/files') to build upon. + - ``params``: A dictionary of parameters (name to value). [optional] + + Returns: + - The full API URL. + """ + return "https://%s%s" % (host, self.build_path(target, params)) + +class DropboxSession(BaseSession): + + def set_token(self, access_token, access_token_secret): + """Attach an access token to the DropboxSession. + + Note that the access 'token' is made up of both a token string + and a secret string. + """ + self.token = OAuthToken(access_token, access_token_secret) + + def set_request_token(self, request_token, request_token_secret): + """Attach an request token to the DropboxSession. + + Note that the request 'token' is made up of both a token string + and a secret string. + """ + self.request_token = OAuthToken(request_token, request_token_secret) + + def build_authorize_url(self, request_token, oauth_callback=None): + """Build a request token authorization URL. + + After obtaining a request token, you'll need to send the user to + the URL returned from this function so that they can confirm that + they want to connect their account to your app. + + Args: + - ``request_token``: A request token from obtain_request_token. + - ``oauth_callback``: A url to redirect back to with the authorized + request token. + + Returns: + - An authorization for the given request token. + """ + params = {'oauth_token': request_token.key, + } + + if oauth_callback: + params['oauth_callback'] = oauth_callback + + return self.build_url(self.WEB_HOST, '/oauth/authorize', params) + + def obtain_request_token(self): + """Obtain a request token from the Dropbox API. + + This is your first step in the OAuth process. You call this to get a + request_token from the Dropbox server that you can then use with + DropboxSession.build_authorize_url() to get the user to authorize it. + After it's authorized you use this token with + DropboxSession.obtain_access_token() to get an access token. + + NOTE: You should only need to do this once for each user, and then you + can store the access token for that user for later operations. + + Returns: + - An :py:class:`OAuthToken` object representing the + request token Dropbox assigned to this app. Also attaches the + request token as self.request_token. + """ + self.token = None # clear any token currently on the request + url = self.build_url(self.API_HOST, '/oauth/request_token') + headers, params = self.build_access_headers('POST', url) + + response = self.rest_client.POST(url, headers=headers, params=params, raw_response=True) + self.request_token = self._parse_token(response.read()) + return self.request_token + + def obtain_access_token(self, request_token=None): + """Obtain an access token for a user. + + After you get a request token, and then send the user to the authorize + URL, you can use the authorized request token with this method to get the + access token to use for future operations. The access token is stored on + the session object. + + Args: + - ``request_token``: A request token from obtain_request_token. [optional] + The request_token should have been authorized via the + authorization url from build_authorize_url. If you don't pass + a request_token, the fallback is self.request_token, which + will exist if you previously called obtain_request_token on this + DropboxSession instance. + + Returns: + - An :py:class:`OAuthToken` object with fields ``key`` and ``secret`` + representing the access token Dropbox assigned to this app and + user. Also attaches the access token as self.token. + """ + request_token = request_token or self.request_token + assert request_token, "No request_token available on the session. Please pass one." + url = self.build_url(self.API_HOST, '/oauth/access_token') + headers, params = self.build_access_headers('POST', url, request_token=request_token) + + response = self.rest_client.POST(url, headers=headers, params=params, raw_response=True) + self.token = self._parse_token(response.read()) + return self.token + + def build_access_headers(self, method, resource_url, params=None, request_token=None): + """Build OAuth access headers for a future request. + + Args: + - ``method``: The HTTP method being used (e.g. 'GET' or 'POST'). + - ``resource_url``: The full url the request will be made to. + - ``params``: A dictionary of parameters to add to what's already on the url. + Typically, this would consist of POST parameters. + + Returns: + - A tuple of (header_dict, params) where header_dict is a dictionary + of header names and values appropriate for passing into dropbox.rest.RESTClient + and params is a dictionary like the one that was passed in, but augmented with + oauth-related parameters as appropriate. + """ + if params is None: + params = {} + else: + params = params.copy() + + oauth_params = { + 'oauth_consumer_key' : self.consumer_creds.key, + 'oauth_timestamp' : self._generate_oauth_timestamp(), + 'oauth_nonce' : self._generate_oauth_nonce(), + 'oauth_version' : self._oauth_version(), + } + + token = request_token if request_token is not None else self.token + + if token: + oauth_params['oauth_token'] = token.key + + self._oauth_sign_request(oauth_params, self.consumer_creds, token) + + params.update(oauth_params) + + return {}, params + + @classmethod + def _oauth_sign_request(cls, params, consumer_pair, token_pair): + params.update({'oauth_signature_method' : 'PLAINTEXT', + 'oauth_signature' : ('%s&%s' % (consumer_pair.secret, token_pair.secret) + if token_pair is not None else + '%s&' % (consumer_pair.secret,))}) + + @classmethod + def _generate_oauth_timestamp(cls): + return int(time.time()) + + @classmethod + def _generate_oauth_nonce(cls, length=8): + return ''.join([str(random.randint(0, 9)) for i in range(length)]) + + @classmethod + def _oauth_version(cls): + return '1.0' + + @classmethod + def _parse_token(cls, s): + if not s: + raise ValueError("Invalid parameter string.") + + params = parse_qs(s, keep_blank_values=False) + if not params: + raise ValueError("Invalid parameter string: %r" % s) + + try: + key = params['oauth_token'][0] + except Exception: + raise ValueError("'oauth_token' not found in OAuth request.") + + try: + secret = params['oauth_token_secret'][0] + except Exception: + raise ValueError("'oauth_token_secret' not found in " + "OAuth request.") + + return OAuthToken(key, secret) + +# Don't use this class directly. +class DropboxOAuth2Session(BaseSession): + + def __init__(self, oauth2_access_token, locale, rest_client=rest.RESTClient): + super(DropboxOAuth2Session, self).__init__("", "", "auto", locale=locale, rest_client=rest_client) + self.access_token = oauth2_access_token + + def build_access_headers(self, method, resource_url, params=None, token=None): + assert token is None + headers = {"Authorization": "Bearer " + self.access_token} + return headers, params diff -Nru gtkrawgallery-0.9.8/src/dropbox/six.py gtkrawgallery-0.9.9/src/dropbox/six.py --- gtkrawgallery-0.9.8/src/dropbox/six.py 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/six.py 2013-09-24 08:49:58.000000000 +0000 @@ -0,0 +1,11 @@ +import sys + +def b(str_): + if sys.version_info >= (3,): + str_ = str_.encode('latin1') + return str_ + +def u(str_): + if sys.version_info < (3,): + str_ = str_.decode('latin1') + return str_ diff -Nru gtkrawgallery-0.9.8/src/dropbox/trusted-certs.crt gtkrawgallery-0.9.9/src/dropbox/trusted-certs.crt --- gtkrawgallery-0.9.8/src/dropbox/trusted-certs.crt 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/trusted-certs.crt 2013-07-07 20:43:34.000000000 +0000 @@ -0,0 +1,341 @@ +# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com +# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress= server-certs@thawte.com +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm +MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx +MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 +dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl +cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 +DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 +yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX +L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj +EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG +7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e +QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ +qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- +# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com +# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- +# Subject: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority +# Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDJQM89Q0VbzXIGtZVxPyCUwDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTIwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f +zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi +TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBAEtEZmBoZOSYG/OwcuaViXzde7OVwB0u2NgZ0C00PcZQ +mhCGjKo/O6gE/DdSlcPZydvN8oYGxLEb8IKIMEKOF1AcZHq4PplJdJf8rAJD+5YM +VgQlDHx8h50kp9jwMim1pN9dokzFFjKoQvZFprY2ueC/ZTaTwtLXa9zeWdaiNfhF +-----END CERTIFICATE----- +# Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority +# Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- +# Subject: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority +# Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority +-----BEGIN CERTIFICATE----- +MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD +VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0 +MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV +BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy +dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ +ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII +0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI +uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI +hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3 +YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc +1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== +-----END CERTIFICATE----- +# Subject: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1 +# Issuer: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT +ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw +MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj +dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l +c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC +UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc +58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ +o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr +aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA +A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA +Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv +8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- +# Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware +# Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== +-----END CERTIFICATE----- +# Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority +# Issuer: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- +# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority +# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- +# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository, CN=Go Daddy Secure Certification Authority/serialNumber=07969287 +# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority +-----BEGIN CERTIFICATE----- +MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx +ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw +MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH +QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j +b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j +b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H +KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm +VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR +SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT +cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ +6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu +MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS +kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB +BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f +BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv +c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH +AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO +BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG +OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU +A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o +0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX +RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH +qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV +U+4= +-----END CERTIFICATE----- +# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2 +# Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2 +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- +# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA +# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- +# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority +# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- +# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority +# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com +-----BEGIN CERTIFICATE----- +MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh +bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu +Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g +QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe +BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX +DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE +YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC +ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q +N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO +r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN +f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH +U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU +TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb +VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg +SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv +biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg +MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw +AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv +ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu +Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd +IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv +bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1 +QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O +WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf +SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw== +-----END CERTIFICATE----- +# Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com +# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy +NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY +dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 +WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS +v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v +UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu +IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC +W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +-----END CERTIFICATE----- + + diff -Nru gtkrawgallery-0.9.8/src/dropbox/util.py gtkrawgallery-0.9.9/src/dropbox/util.py --- gtkrawgallery-0.9.8/src/dropbox/util.py 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/dropbox/util.py 2013-09-24 08:49:58.000000000 +0000 @@ -0,0 +1,53 @@ +import os + +class AnalyzeFileObjBug(Exception): + msg = ("\n" + "Expected file object to have %d bytes, instead we read %d bytes.\n" + "File size detection may have failed (see dropbox.util.AnalyzeFileObj)\n") + def __init__(self, expected, actual): + self.expected = expected + self.actual = actual + + def __str__(self): + return self.msg % (self.expected, self.actual) + +def analyze_file_obj(obj): + """ Get the size and contents of a file-like object. + Returns: (size, raw_data) + size: The amount of data waiting to be read + raw_data: If not None, the entire contents of the stream (as a string). + None if the stream should be read() in chunks. + """ + pos = 0 + if hasattr(obj, 'tell'): + pos = obj.tell() + + # Handle cStringIO and StringIO + if hasattr(obj, 'getvalue'): + # Why using getvalue() makes sense: + # For StringIO, this string is pre-computed anyway by read(). + # For cStringIO, getvalue() is the only way + # to determine the length without read()'ing the whole thing. + raw_data = obj.getvalue() + if pos == 0: + return (len(raw_data), raw_data) + else: + # We could return raw_data[pos:], but that could drastically + # increase memory usage. Better to read it block at a time. + size = max(0, len(raw_data) - pos) + return (size, None) + + # Handle real files + if hasattr(obj, 'fileno'): + size = max(0, os.fstat(obj.fileno()).st_size - pos) + return (size, None) + + # User-defined object with len() + if hasattr(obj, '__len__'): + size = max(0, len(obj) - pos) + return (size, None) + + # We don't know what kind of stream this is. + # To determine the size, we must read the whole thing. + raw_data = obj.read() + return (len(raw_data), raw_data) diff -Nru gtkrawgallery-0.9.8/src/flickrapi/__init__.py gtkrawgallery-0.9.9/src/flickrapi/__init__.py --- gtkrawgallery-0.9.8/src/flickrapi/__init__.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/flickrapi/__init__.py 2013-09-24 08:49:58.000000000 +0000 @@ -264,9 +264,9 @@ import xml.etree.ElementTree as ElementTree except ImportError: # For Python 2.4 compatibility: - try: - import elementtree.ElementTree as ElementTree - except ImportError: +## try: +## import elementtree.ElementTree as ElementTree +## except ImportError: raise ImportError("You need to install " "ElementTree for using the etree format") diff -Nru gtkrawgallery-0.9.8/src/gdata/oauth/__init__.py gtkrawgallery-0.9.9/src/gdata/oauth/__init__.py --- gtkrawgallery-0.9.8/src/gdata/oauth/__init__.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/oauth/__init__.py 2013-09-24 08:50:01.000000000 +0000 @@ -1,529 +1,529 @@ -import cgi -import urllib -import time -import random -import urlparse -import hmac -import binascii - -VERSION = '1.0' # Hi Blaine! -HTTP_METHOD = 'GET' -SIGNATURE_METHOD = 'PLAINTEXT' - -# Generic exception class -class OAuthError(RuntimeError): - def __init__(self, message='OAuth error occured.'): - self.message = message - -# optional WWW-Authenticate header (401 error) -def build_authenticate_header(realm=''): - return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} - -# url escape -def escape(s): - # escape '/' too - return urllib.quote(s, safe='~') - -# util function: current timestamp -# seconds since epoch (UTC) -def generate_timestamp(): - return int(time.time()) - -# util function: nonce -# pseudorandom number -def generate_nonce(length=8): - return ''.join([str(random.randint(0, 9)) for i in range(length)]) - -# OAuthConsumer is a data type that represents the identity of the Consumer -# via its shared secret with the Service Provider. -class OAuthConsumer(object): - key = None - secret = None - - def __init__(self, key, secret): - self.key = key - self.secret = secret - -# OAuthToken is a data type that represents an End User via either an access -# or request token. -class OAuthToken(object): - # access tokens and request tokens - key = None - secret = None - - ''' - key = the token - secret = the token secret - ''' - def __init__(self, key, secret): - self.key = key - self.secret = secret - - def to_string(self): - return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) - - # return a token from something like: - # oauth_token_secret=digg&oauth_token=digg - def from_string(s): - params = cgi.parse_qs(s, keep_blank_values=False) - key = params['oauth_token'][0] - secret = params['oauth_token_secret'][0] - return OAuthToken(key, secret) - from_string = staticmethod(from_string) - - def __str__(self): - return self.to_string() - -# OAuthRequest represents the request and can be serialized -class OAuthRequest(object): - ''' - OAuth parameters: - - oauth_consumer_key - - oauth_token - - oauth_signature_method - - oauth_signature - - oauth_timestamp - - oauth_nonce - - oauth_version - ... any additional parameters, as defined by the Service Provider. - ''' - parameters = None # oauth parameters - http_method = HTTP_METHOD - http_url = None - version = VERSION - - def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): - self.http_method = http_method - self.http_url = http_url - self.parameters = parameters or {} - - def set_parameter(self, parameter, value): - self.parameters[parameter] = value - - def get_parameter(self, parameter): - try: - return self.parameters[parameter] - except: - raise OAuthError('Parameter not found: %s' % parameter) - - def _get_timestamp_nonce(self): - return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce') - - # get any non-oauth parameters - def get_nonoauth_parameters(self): - parameters = {} - for k, v in self.parameters.iteritems(): - # ignore oauth parameters - if k.find('oauth_') < 0: - parameters[k] = v - return parameters - - # serialize as a header for an HTTPAuth request - def to_header(self, realm=''): - auth_header = 'OAuth realm="%s"' % realm - # add the oauth parameters - if self.parameters: - for k, v in self.parameters.iteritems(): - if k[:6] == 'oauth_': - auth_header += ', %s="%s"' % (k, escape(str(v))) - return {'Authorization': auth_header} - - # serialize as post data for a POST request - def to_postdata(self): - return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()]) - - # serialize as a url for a GET request - def to_url(self): - return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) - - # return a string that consists of all the parameters that need to be signed - def get_normalized_parameters(self): - params = self.parameters - try: - # exclude the signature if it exists - del params['oauth_signature'] - except: - pass - key_values = params.items() - # sort lexicographically, first after key, then after value - key_values.sort() - # combine key value pairs in string and escape - return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values]) - - # just uppercases the http method - def get_normalized_http_method(self): - return self.http_method.upper() - - # parses the url and rebuilds it to be scheme://host/path - def get_normalized_http_url(self): - parts = urlparse.urlparse(self.http_url) - host = parts[1].lower() - if host.endswith(':80') or host.endswith(':443'): - host = host.split(':')[0] - url_string = '%s://%s%s' % (parts[0], host, parts[2]) # scheme, netloc, path - return url_string - - # set the signature parameter to the result of build_signature - def sign_request(self, signature_method, consumer, token): - # set the signature method - self.set_parameter('oauth_signature_method', signature_method.get_name()) - # set the signature - self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) - - def build_signature(self, signature_method, consumer, token): - # call the build signature method within the signature method - return signature_method.build_signature(self, consumer, token) - - def from_request(http_method, http_url, headers=None, parameters=None, query_string=None): - # combine multiple parameter sources - if parameters is None: - parameters = {} - - # headers - if headers and 'Authorization' in headers: - auth_header = headers['Authorization'] - # check that the authorization header is OAuth - if auth_header.index('OAuth') > -1: - try: - # get the parameters from the header - header_params = OAuthRequest._split_header(auth_header) - parameters.update(header_params) - except: - raise OAuthError('Unable to parse OAuth parameters from Authorization header.') - - # GET or POST query string - if query_string: - query_params = OAuthRequest._split_url_string(query_string) - parameters.update(query_params) - - # URL parameters - param_str = urlparse.urlparse(http_url)[4] # query - url_params = OAuthRequest._split_url_string(param_str) - parameters.update(url_params) - - if parameters: - return OAuthRequest(http_method, http_url, parameters) - - return None - from_request = staticmethod(from_request) - - def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): - if not parameters: - parameters = {} - - defaults = { - 'oauth_consumer_key': oauth_consumer.key, - 'oauth_timestamp': generate_timestamp(), - 'oauth_nonce': generate_nonce(), - 'oauth_version': OAuthRequest.version, - } - - defaults.update(parameters) - parameters = defaults - - if token: - parameters['oauth_token'] = token.key - - return OAuthRequest(http_method, http_url, parameters) - from_consumer_and_token = staticmethod(from_consumer_and_token) - - def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): - if not parameters: - parameters = {} - - parameters['oauth_token'] = token.key - - if callback: - parameters['oauth_callback'] = callback - - return OAuthRequest(http_method, http_url, parameters) - from_token_and_callback = staticmethod(from_token_and_callback) - - # util function: turn Authorization: header into parameters, has to do some unescaping - def _split_header(header): - params = {} - parts = header[6:].split(',') - for param in parts: - # ignore realm parameter - if param.find('realm') > -1: - continue - # remove whitespace - param = param.strip() - # split key-value - param_parts = param.split('=', 1) - # remove quotes and unescape the value - params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) - return params - _split_header = staticmethod(_split_header) - - # util function: turn url string into parameters, has to do some unescaping - # even empty values should be included - def _split_url_string(param_str): - parameters = cgi.parse_qs(param_str, keep_blank_values=True) - for k, v in parameters.iteritems(): - parameters[k] = urllib.unquote(v[0]) - return parameters - _split_url_string = staticmethod(_split_url_string) - -# OAuthServer is a worker to check a requests validity against a data store -class OAuthServer(object): - timestamp_threshold = 300 # in seconds, five minutes - version = VERSION - signature_methods = None - data_store = None - - def __init__(self, data_store=None, signature_methods=None): - self.data_store = data_store - self.signature_methods = signature_methods or {} - - def set_data_store(self, oauth_data_store): - self.data_store = oauth_data_store - - def get_data_store(self): - return self.data_store - - def add_signature_method(self, signature_method): - self.signature_methods[signature_method.get_name()] = signature_method - return self.signature_methods - - # process a request_token request - # returns the request token on success - def fetch_request_token(self, oauth_request): - try: - # get the request token for authorization - token = self._get_token(oauth_request, 'request') - except OAuthError: - # no token required for the initial token request - version = self._get_version(oauth_request) - consumer = self._get_consumer(oauth_request) - self._check_signature(oauth_request, consumer, None) - # fetch a new token - token = self.data_store.fetch_request_token(consumer) - return token - - # process an access_token request - # returns the access token on success - def fetch_access_token(self, oauth_request): - version = self._get_version(oauth_request) - consumer = self._get_consumer(oauth_request) - # get the request token - token = self._get_token(oauth_request, 'request') - self._check_signature(oauth_request, consumer, token) - new_token = self.data_store.fetch_access_token(consumer, token) - return new_token - - # verify an api call, checks all the parameters - def verify_request(self, oauth_request): - # -> consumer and token - version = self._get_version(oauth_request) - consumer = self._get_consumer(oauth_request) - # get the access token - token = self._get_token(oauth_request, 'access') - self._check_signature(oauth_request, consumer, token) - parameters = oauth_request.get_nonoauth_parameters() - return consumer, token, parameters - - # authorize a request token - def authorize_token(self, token, user): - return self.data_store.authorize_request_token(token, user) - - # get the callback url - def get_callback(self, oauth_request): - return oauth_request.get_parameter('oauth_callback') - - # optional support for the authenticate header - def build_authenticate_header(self, realm=''): - return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} - - # verify the correct version request for this server - def _get_version(self, oauth_request): - try: - version = oauth_request.get_parameter('oauth_version') - except: - version = VERSION - if version and version != self.version: - raise OAuthError('OAuth version %s not supported.' % str(version)) - return version - - # figure out the signature with some defaults - def _get_signature_method(self, oauth_request): - try: - signature_method = oauth_request.get_parameter('oauth_signature_method') - except: - signature_method = SIGNATURE_METHOD - try: - # get the signature method object - signature_method = self.signature_methods[signature_method] - except: - signature_method_names = ', '.join(self.signature_methods.keys()) - raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) - - return signature_method - - def _get_consumer(self, oauth_request): - consumer_key = oauth_request.get_parameter('oauth_consumer_key') - if not consumer_key: - raise OAuthError('Invalid consumer key.') - consumer = self.data_store.lookup_consumer(consumer_key) - if not consumer: - raise OAuthError('Invalid consumer.') - return consumer - - # try to find the token for the provided request token key - def _get_token(self, oauth_request, token_type='access'): - token_field = oauth_request.get_parameter('oauth_token') - consumer = self._get_consumer(oauth_request) - token = self.data_store.lookup_token(consumer, token_type, token_field) - if not token: - raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) - return token - - def _check_signature(self, oauth_request, consumer, token): - timestamp, nonce = oauth_request._get_timestamp_nonce() - self._check_timestamp(timestamp) - self._check_nonce(consumer, token, nonce) - signature_method = self._get_signature_method(oauth_request) - try: - signature = oauth_request.get_parameter('oauth_signature') - except: - raise OAuthError('Missing signature.') - # validate the signature - valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature) - if not valid_sig: - key, base = signature_method.build_signature_base_string(oauth_request, consumer, token) - raise OAuthError('Invalid signature. Expected signature base string: %s' % base) - built = signature_method.build_signature(oauth_request, consumer, token) - - def _check_timestamp(self, timestamp): - # verify that timestamp is recentish - timestamp = int(timestamp) - now = int(time.time()) - lapsed = now - timestamp - if lapsed > self.timestamp_threshold: - raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) - - def _check_nonce(self, consumer, token, nonce): - # verify that the nonce is uniqueish - nonce = self.data_store.lookup_nonce(consumer, token, nonce) - if nonce: - raise OAuthError('Nonce already used: %s' % str(nonce)) - -# OAuthClient is a worker to attempt to execute a request -class OAuthClient(object): - consumer = None - token = None - - def __init__(self, oauth_consumer, oauth_token): - self.consumer = oauth_consumer - self.token = oauth_token - - def get_consumer(self): - return self.consumer - - def get_token(self): - return self.token - - def fetch_request_token(self, oauth_request): - # -> OAuthToken - raise NotImplementedError - - def fetch_access_token(self, oauth_request): - # -> OAuthToken - raise NotImplementedError - - def access_resource(self, oauth_request): - # -> some protected resource - raise NotImplementedError - -# OAuthDataStore is a database abstraction used to lookup consumers and tokens -class OAuthDataStore(object): - - def lookup_consumer(self, key): - # -> OAuthConsumer - raise NotImplementedError - - def lookup_token(self, oauth_consumer, token_type, token_token): - # -> OAuthToken - raise NotImplementedError - - def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): - # -> OAuthToken - raise NotImplementedError - - def fetch_request_token(self, oauth_consumer): - # -> OAuthToken - raise NotImplementedError - - def fetch_access_token(self, oauth_consumer, oauth_token): - # -> OAuthToken - raise NotImplementedError - - def authorize_request_token(self, oauth_token, user): - # -> OAuthToken - raise NotImplementedError - -# OAuthSignatureMethod is a strategy class that implements a signature method -class OAuthSignatureMethod(object): - def get_name(self): - # -> str - raise NotImplementedError - - def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token): - # -> str key, str raw - raise NotImplementedError - - def build_signature(self, oauth_request, oauth_consumer, oauth_token): - # -> str - raise NotImplementedError - - def check_signature(self, oauth_request, consumer, token, signature): - built = self.build_signature(oauth_request, consumer, token) - return built == signature - -class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): - - def get_name(self): - return 'HMAC-SHA1' - - def build_signature_base_string(self, oauth_request, consumer, token): - sig = ( - escape(oauth_request.get_normalized_http_method()), - escape(oauth_request.get_normalized_http_url()), - escape(oauth_request.get_normalized_parameters()), - ) - - key = '%s&' % escape(consumer.secret) - if token: - key += escape(token.secret) - raw = '&'.join(sig) - return key, raw - - def build_signature(self, oauth_request, consumer, token): - # build the base signature string - key, raw = self.build_signature_base_string(oauth_request, consumer, token) - - # hmac object - try: - import hashlib # 2.5 - hashed = hmac.new(key, raw, hashlib.sha1) - except: - import sha # deprecated - hashed = hmac.new(key, raw, sha) - - # calculate the digest base 64 - return binascii.b2a_base64(hashed.digest())[:-1] - -class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): - - def get_name(self): - return 'PLAINTEXT' - - def build_signature_base_string(self, oauth_request, consumer, token): - # concatenate the consumer key and secret - sig = escape(consumer.secret) + '&' - if token: - sig = sig + escape(token.secret) - return sig - - def build_signature(self, oauth_request, consumer, token): - return self.build_signature_base_string(oauth_request, consumer, token) +import cgi +import urllib +import time +import random +import urlparse +import hmac +import binascii + +VERSION = '1.0' # Hi Blaine! +HTTP_METHOD = 'GET' +SIGNATURE_METHOD = 'PLAINTEXT' + +# Generic exception class +class OAuthError(RuntimeError): + def __init__(self, message='OAuth error occured.'): + self.message = message + +# optional WWW-Authenticate header (401 error) +def build_authenticate_header(realm=''): + return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} + +# url escape +def escape(s): + # escape '/' too + return urllib.quote(s, safe='~') + +# util function: current timestamp +# seconds since epoch (UTC) +def generate_timestamp(): + return int(time.time()) + +# util function: nonce +# pseudorandom number +def generate_nonce(length=8): + return ''.join([str(random.randint(0, 9)) for i in range(length)]) + +# OAuthConsumer is a data type that represents the identity of the Consumer +# via its shared secret with the Service Provider. +class OAuthConsumer(object): + key = None + secret = None + + def __init__(self, key, secret): + self.key = key + self.secret = secret + +# OAuthToken is a data type that represents an End User via either an access +# or request token. +class OAuthToken(object): + # access tokens and request tokens + key = None + secret = None + + ''' + key = the token + secret = the token secret + ''' + def __init__(self, key, secret): + self.key = key + self.secret = secret + + def to_string(self): + return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) + + # return a token from something like: + # oauth_token_secret=digg&oauth_token=digg + def from_string(s): + params = cgi.parse_qs(s, keep_blank_values=False) + key = params['oauth_token'][0] + secret = params['oauth_token_secret'][0] + return OAuthToken(key, secret) + from_string = staticmethod(from_string) + + def __str__(self): + return self.to_string() + +# OAuthRequest represents the request and can be serialized +class OAuthRequest(object): + ''' + OAuth parameters: + - oauth_consumer_key + - oauth_token + - oauth_signature_method + - oauth_signature + - oauth_timestamp + - oauth_nonce + - oauth_version + ... any additional parameters, as defined by the Service Provider. + ''' + parameters = None # oauth parameters + http_method = HTTP_METHOD + http_url = None + version = VERSION + + def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): + self.http_method = http_method + self.http_url = http_url + self.parameters = parameters or {} + + def set_parameter(self, parameter, value): + self.parameters[parameter] = value + + def get_parameter(self, parameter): + try: + return self.parameters[parameter] + except: + raise OAuthError('Parameter not found: %s' % parameter) + + def _get_timestamp_nonce(self): + return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce') + + # get any non-oauth parameters + def get_nonoauth_parameters(self): + parameters = {} + for k, v in self.parameters.iteritems(): + # ignore oauth parameters + if k.find('oauth_') < 0: + parameters[k] = v + return parameters + + # serialize as a header for an HTTPAuth request + def to_header(self, realm=''): + auth_header = 'OAuth realm="%s"' % realm + # add the oauth parameters + if self.parameters: + for k, v in self.parameters.iteritems(): + if k[:6] == 'oauth_': + auth_header += ', %s="%s"' % (k, escape(str(v))) + return {'Authorization': auth_header} + + # serialize as post data for a POST request + def to_postdata(self): + return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()]) + + # serialize as a url for a GET request + def to_url(self): + return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) + + # return a string that consists of all the parameters that need to be signed + def get_normalized_parameters(self): + params = self.parameters + try: + # exclude the signature if it exists + del params['oauth_signature'] + except: + pass + key_values = params.items() + # sort lexicographically, first after key, then after value + key_values.sort() + # combine key value pairs in string and escape + return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values]) + + # just uppercases the http method + def get_normalized_http_method(self): + return self.http_method.upper() + + # parses the url and rebuilds it to be scheme://host/path + def get_normalized_http_url(self): + parts = urlparse.urlparse(self.http_url) + host = parts[1].lower() + if host.endswith(':80') or host.endswith(':443'): + host = host.split(':')[0] + url_string = '%s://%s%s' % (parts[0], host, parts[2]) # scheme, netloc, path + return url_string + + # set the signature parameter to the result of build_signature + def sign_request(self, signature_method, consumer, token): + # set the signature method + self.set_parameter('oauth_signature_method', signature_method.get_name()) + # set the signature + self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) + + def build_signature(self, signature_method, consumer, token): + # call the build signature method within the signature method + return signature_method.build_signature(self, consumer, token) + + def from_request(http_method, http_url, headers=None, parameters=None, query_string=None): + # combine multiple parameter sources + if parameters is None: + parameters = {} + + # headers + if headers and 'Authorization' in headers: + auth_header = headers['Authorization'] + # check that the authorization header is OAuth + if auth_header.index('OAuth') > -1: + try: + # get the parameters from the header + header_params = OAuthRequest._split_header(auth_header) + parameters.update(header_params) + except: + raise OAuthError('Unable to parse OAuth parameters from Authorization header.') + + # GET or POST query string + if query_string: + query_params = OAuthRequest._split_url_string(query_string) + parameters.update(query_params) + + # URL parameters + param_str = urlparse.urlparse(http_url)[4] # query + url_params = OAuthRequest._split_url_string(param_str) + parameters.update(url_params) + + if parameters: + return OAuthRequest(http_method, http_url, parameters) + + return None + from_request = staticmethod(from_request) + + def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): + if not parameters: + parameters = {} + + defaults = { + 'oauth_consumer_key': oauth_consumer.key, + 'oauth_timestamp': generate_timestamp(), + 'oauth_nonce': generate_nonce(), + 'oauth_version': OAuthRequest.version, + } + + defaults.update(parameters) + parameters = defaults + + if token: + parameters['oauth_token'] = token.key + + return OAuthRequest(http_method, http_url, parameters) + from_consumer_and_token = staticmethod(from_consumer_and_token) + + def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): + if not parameters: + parameters = {} + + parameters['oauth_token'] = token.key + + if callback: + parameters['oauth_callback'] = callback + + return OAuthRequest(http_method, http_url, parameters) + from_token_and_callback = staticmethod(from_token_and_callback) + + # util function: turn Authorization: header into parameters, has to do some unescaping + def _split_header(header): + params = {} + parts = header[6:].split(',') + for param in parts: + # ignore realm parameter + if param.find('realm') > -1: + continue + # remove whitespace + param = param.strip() + # split key-value + param_parts = param.split('=', 1) + # remove quotes and unescape the value + params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) + return params + _split_header = staticmethod(_split_header) + + # util function: turn url string into parameters, has to do some unescaping + # even empty values should be included + def _split_url_string(param_str): + parameters = cgi.parse_qs(param_str, keep_blank_values=True) + for k, v in parameters.iteritems(): + parameters[k] = urllib.unquote(v[0]) + return parameters + _split_url_string = staticmethod(_split_url_string) + +# OAuthServer is a worker to check a requests validity against a data store +class OAuthServer(object): + timestamp_threshold = 300 # in seconds, five minutes + version = VERSION + signature_methods = None + data_store = None + + def __init__(self, data_store=None, signature_methods=None): + self.data_store = data_store + self.signature_methods = signature_methods or {} + + def set_data_store(self, oauth_data_store): + self.data_store = oauth_data_store + + def get_data_store(self): + return self.data_store + + def add_signature_method(self, signature_method): + self.signature_methods[signature_method.get_name()] = signature_method + return self.signature_methods + + # process a request_token request + # returns the request token on success + def fetch_request_token(self, oauth_request): + try: + # get the request token for authorization + token = self._get_token(oauth_request, 'request') + except OAuthError: + # no token required for the initial token request + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + self._check_signature(oauth_request, consumer, None) + # fetch a new token + token = self.data_store.fetch_request_token(consumer) + return token + + # process an access_token request + # returns the access token on success + def fetch_access_token(self, oauth_request): + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + # get the request token + token = self._get_token(oauth_request, 'request') + self._check_signature(oauth_request, consumer, token) + new_token = self.data_store.fetch_access_token(consumer, token) + return new_token + + # verify an api call, checks all the parameters + def verify_request(self, oauth_request): + # -> consumer and token + version = self._get_version(oauth_request) + consumer = self._get_consumer(oauth_request) + # get the access token + token = self._get_token(oauth_request, 'access') + self._check_signature(oauth_request, consumer, token) + parameters = oauth_request.get_nonoauth_parameters() + return consumer, token, parameters + + # authorize a request token + def authorize_token(self, token, user): + return self.data_store.authorize_request_token(token, user) + + # get the callback url + def get_callback(self, oauth_request): + return oauth_request.get_parameter('oauth_callback') + + # optional support for the authenticate header + def build_authenticate_header(self, realm=''): + return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} + + # verify the correct version request for this server + def _get_version(self, oauth_request): + try: + version = oauth_request.get_parameter('oauth_version') + except: + version = VERSION + if version and version != self.version: + raise OAuthError('OAuth version %s not supported.' % str(version)) + return version + + # figure out the signature with some defaults + def _get_signature_method(self, oauth_request): + try: + signature_method = oauth_request.get_parameter('oauth_signature_method') + except: + signature_method = SIGNATURE_METHOD + try: + # get the signature method object + signature_method = self.signature_methods[signature_method] + except: + signature_method_names = ', '.join(self.signature_methods.keys()) + raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) + + return signature_method + + def _get_consumer(self, oauth_request): + consumer_key = oauth_request.get_parameter('oauth_consumer_key') + if not consumer_key: + raise OAuthError('Invalid consumer key.') + consumer = self.data_store.lookup_consumer(consumer_key) + if not consumer: + raise OAuthError('Invalid consumer.') + return consumer + + # try to find the token for the provided request token key + def _get_token(self, oauth_request, token_type='access'): + token_field = oauth_request.get_parameter('oauth_token') + consumer = self._get_consumer(oauth_request) + token = self.data_store.lookup_token(consumer, token_type, token_field) + if not token: + raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) + return token + + def _check_signature(self, oauth_request, consumer, token): + timestamp, nonce = oauth_request._get_timestamp_nonce() + self._check_timestamp(timestamp) + self._check_nonce(consumer, token, nonce) + signature_method = self._get_signature_method(oauth_request) + try: + signature = oauth_request.get_parameter('oauth_signature') + except: + raise OAuthError('Missing signature.') + # validate the signature + valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature) + if not valid_sig: + key, base = signature_method.build_signature_base_string(oauth_request, consumer, token) + raise OAuthError('Invalid signature. Expected signature base string: %s' % base) + built = signature_method.build_signature(oauth_request, consumer, token) + + def _check_timestamp(self, timestamp): + # verify that timestamp is recentish + timestamp = int(timestamp) + now = int(time.time()) + lapsed = now - timestamp + if lapsed > self.timestamp_threshold: + raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) + + def _check_nonce(self, consumer, token, nonce): + # verify that the nonce is uniqueish + nonce = self.data_store.lookup_nonce(consumer, token, nonce) + if nonce: + raise OAuthError('Nonce already used: %s' % str(nonce)) + +# OAuthClient is a worker to attempt to execute a request +class OAuthClient(object): + consumer = None + token = None + + def __init__(self, oauth_consumer, oauth_token): + self.consumer = oauth_consumer + self.token = oauth_token + + def get_consumer(self): + return self.consumer + + def get_token(self): + return self.token + + def fetch_request_token(self, oauth_request): + # -> OAuthToken + raise NotImplementedError + + def fetch_access_token(self, oauth_request): + # -> OAuthToken + raise NotImplementedError + + def access_resource(self, oauth_request): + # -> some protected resource + raise NotImplementedError + +# OAuthDataStore is a database abstraction used to lookup consumers and tokens +class OAuthDataStore(object): + + def lookup_consumer(self, key): + # -> OAuthConsumer + raise NotImplementedError + + def lookup_token(self, oauth_consumer, token_type, token_token): + # -> OAuthToken + raise NotImplementedError + + def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): + # -> OAuthToken + raise NotImplementedError + + def fetch_request_token(self, oauth_consumer): + # -> OAuthToken + raise NotImplementedError + + def fetch_access_token(self, oauth_consumer, oauth_token): + # -> OAuthToken + raise NotImplementedError + + def authorize_request_token(self, oauth_token, user): + # -> OAuthToken + raise NotImplementedError + +# OAuthSignatureMethod is a strategy class that implements a signature method +class OAuthSignatureMethod(object): + def get_name(self): + # -> str + raise NotImplementedError + + def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token): + # -> str key, str raw + raise NotImplementedError + + def build_signature(self, oauth_request, oauth_consumer, oauth_token): + # -> str + raise NotImplementedError + + def check_signature(self, oauth_request, consumer, token, signature): + built = self.build_signature(oauth_request, consumer, token) + return built == signature + +class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): + + def get_name(self): + return 'HMAC-SHA1' + + def build_signature_base_string(self, oauth_request, consumer, token): + sig = ( + escape(oauth_request.get_normalized_http_method()), + escape(oauth_request.get_normalized_http_url()), + escape(oauth_request.get_normalized_parameters()), + ) + + key = '%s&' % escape(consumer.secret) + if token: + key += escape(token.secret) + raw = '&'.join(sig) + return key, raw + + def build_signature(self, oauth_request, consumer, token): + # build the base signature string + key, raw = self.build_signature_base_string(oauth_request, consumer, token) + + # hmac object + try: + import hashlib # 2.5 + hashed = hmac.new(key, raw, hashlib.sha1) + except: + import sha # deprecated + hashed = hmac.new(key, raw, sha) + + # calculate the digest base 64 + return binascii.b2a_base64(hashed.digest())[:-1] + +class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): + + def get_name(self): + return 'PLAINTEXT' + + def build_signature_base_string(self, oauth_request, consumer, token): + # concatenate the consumer key and secret + sig = escape(consumer.secret) + '&' + if token: + sig = sig + escape(token.secret) + return sig + + def build_signature(self, oauth_request, consumer, token): + return self.build_signature_base_string(oauth_request, consumer, token) diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/BaseDB.py gtkrawgallery-0.9.9/src/gdata/tlslite/BaseDB.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/BaseDB.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/BaseDB.py 2013-09-24 08:50:01.000000000 +0000 @@ -1,120 +1,120 @@ -"""Base class for SharedKeyDB and VerifierDB.""" - -import anydbm -import thread - -class BaseDB: - def __init__(self, filename, type): - self.type = type - self.filename = filename - if self.filename: - self.db = None - else: - self.db = {} - self.lock = thread.allocate_lock() - - def create(self): - """Create a new on-disk database. - - @raise anydbm.error: If there's a problem creating the database. - """ - if self.filename: - self.db = anydbm.open(self.filename, "n") #raises anydbm.error - self.db["--Reserved--type"] = self.type - self.db.sync() - else: - self.db = {} - - def open(self): - """Open a pre-existing on-disk database. - - @raise anydbm.error: If there's a problem opening the database. - @raise ValueError: If the database is not of the right type. - """ - if not self.filename: - raise ValueError("Can only open on-disk databases") - self.db = anydbm.open(self.filename, "w") #raises anydbm.error - try: - if self.db["--Reserved--type"] != self.type: - raise ValueError("Not a %s database" % self.type) - except KeyError: - raise ValueError("Not a recognized database") - - def __getitem__(self, username): - if self.db == None: - raise AssertionError("DB not open") - - self.lock.acquire() - try: - valueStr = self.db[username] - finally: - self.lock.release() - - return self._getItem(username, valueStr) - - def __setitem__(self, username, value): - if self.db == None: - raise AssertionError("DB not open") - - valueStr = self._setItem(username, value) - - self.lock.acquire() - try: - self.db[username] = valueStr - if self.filename: - self.db.sync() - finally: - self.lock.release() - - def __delitem__(self, username): - if self.db == None: - raise AssertionError("DB not open") - - self.lock.acquire() - try: - del(self.db[username]) - if self.filename: - self.db.sync() - finally: - self.lock.release() - - def __contains__(self, username): - """Check if the database contains the specified username. - - @type username: str - @param username: The username to check for. - - @rtype: bool - @return: True if the database contains the username, False - otherwise. - - """ - if self.db == None: - raise AssertionError("DB not open") - - self.lock.acquire() - try: - return self.db.has_key(username) - finally: - self.lock.release() - - def check(self, username, param): - value = self.__getitem__(username) - return self._checkItem(value, username, param) - - def keys(self): - """Return a list of usernames in the database. - - @rtype: list - @return: The usernames in the database. - """ - if self.db == None: - raise AssertionError("DB not open") - - self.lock.acquire() - try: - usernames = self.db.keys() - finally: - self.lock.release() - usernames = [u for u in usernames if not u.startswith("--Reserved--")] +"""Base class for SharedKeyDB and VerifierDB.""" + +import anydbm +import thread + +class BaseDB: + def __init__(self, filename, type): + self.type = type + self.filename = filename + if self.filename: + self.db = None + else: + self.db = {} + self.lock = thread.allocate_lock() + + def create(self): + """Create a new on-disk database. + + @raise anydbm.error: If there's a problem creating the database. + """ + if self.filename: + self.db = anydbm.open(self.filename, "n") #raises anydbm.error + self.db["--Reserved--type"] = self.type + self.db.sync() + else: + self.db = {} + + def open(self): + """Open a pre-existing on-disk database. + + @raise anydbm.error: If there's a problem opening the database. + @raise ValueError: If the database is not of the right type. + """ + if not self.filename: + raise ValueError("Can only open on-disk databases") + self.db = anydbm.open(self.filename, "w") #raises anydbm.error + try: + if self.db["--Reserved--type"] != self.type: + raise ValueError("Not a %s database" % self.type) + except KeyError: + raise ValueError("Not a recognized database") + + def __getitem__(self, username): + if self.db == None: + raise AssertionError("DB not open") + + self.lock.acquire() + try: + valueStr = self.db[username] + finally: + self.lock.release() + + return self._getItem(username, valueStr) + + def __setitem__(self, username, value): + if self.db == None: + raise AssertionError("DB not open") + + valueStr = self._setItem(username, value) + + self.lock.acquire() + try: + self.db[username] = valueStr + if self.filename: + self.db.sync() + finally: + self.lock.release() + + def __delitem__(self, username): + if self.db == None: + raise AssertionError("DB not open") + + self.lock.acquire() + try: + del(self.db[username]) + if self.filename: + self.db.sync() + finally: + self.lock.release() + + def __contains__(self, username): + """Check if the database contains the specified username. + + @type username: str + @param username: The username to check for. + + @rtype: bool + @return: True if the database contains the username, False + otherwise. + + """ + if self.db == None: + raise AssertionError("DB not open") + + self.lock.acquire() + try: + return self.db.has_key(username) + finally: + self.lock.release() + + def check(self, username, param): + value = self.__getitem__(username) + return self._checkItem(value, username, param) + + def keys(self): + """Return a list of usernames in the database. + + @rtype: list + @return: The usernames in the database. + """ + if self.db == None: + raise AssertionError("DB not open") + + self.lock.acquire() + try: + usernames = self.db.keys() + finally: + self.lock.release() + usernames = [u for u in usernames if not u.startswith("--Reserved--")] return usernames \ No newline at end of file diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/X509CertChain.py gtkrawgallery-0.9.9/src/gdata/tlslite/X509CertChain.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/X509CertChain.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/X509CertChain.py 2013-09-24 08:50:01.000000000 +0000 @@ -1,181 +1,181 @@ -"""Class representing an X.509 certificate chain.""" - -from utils import cryptomath - -class X509CertChain: - """This class represents a chain of X.509 certificates. - - @type x509List: list - @ivar x509List: A list of L{tlslite.X509.X509} instances, - starting with the end-entity certificate and with every - subsequent certificate certifying the previous. - """ - - def __init__(self, x509List=None): - """Create a new X509CertChain. - - @type x509List: list - @param x509List: A list of L{tlslite.X509.X509} instances, - starting with the end-entity certificate and with every - subsequent certificate certifying the previous. - """ - if x509List: - self.x509List = x509List - else: - self.x509List = [] - - def getNumCerts(self): - """Get the number of certificates in this chain. - - @rtype: int - """ - return len(self.x509List) - - def getEndEntityPublicKey(self): - """Get the public key from the end-entity certificate. - - @rtype: L{tlslite.utils.RSAKey.RSAKey} - """ - if self.getNumCerts() == 0: - raise AssertionError() - return self.x509List[0].publicKey - - def getFingerprint(self): - """Get the hex-encoded fingerprint of the end-entity certificate. - - @rtype: str - @return: A hex-encoded fingerprint. - """ - if self.getNumCerts() == 0: - raise AssertionError() - return self.x509List[0].getFingerprint() - - def getCommonName(self): - """Get the Subject's Common Name from the end-entity certificate. - - The cryptlib_py module must be installed in order to use this - function. - - @rtype: str or None - @return: The CN component of the certificate's subject DN, if - present. - """ - if self.getNumCerts() == 0: - raise AssertionError() - return self.x509List[0].getCommonName() - - def validate(self, x509TrustList): - """Check the validity of the certificate chain. - - This checks that every certificate in the chain validates with - the subsequent one, until some certificate validates with (or - is identical to) one of the passed-in root certificates. - - The cryptlib_py module must be installed in order to use this - function. - - @type x509TrustList: list of L{tlslite.X509.X509} - @param x509TrustList: A list of trusted root certificates. The - certificate chain must extend to one of these certificates to - be considered valid. - """ - - import cryptlib_py - c1 = None - c2 = None - lastC = None - rootC = None - - try: - rootFingerprints = [c.getFingerprint() for c in x509TrustList] - - #Check that every certificate in the chain validates with the - #next one - for cert1, cert2 in zip(self.x509List, self.x509List[1:]): - - #If we come upon a root certificate, we're done. - if cert1.getFingerprint() in rootFingerprints: - return True - - c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(), - cryptlib_py.CRYPT_UNUSED) - c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(), - cryptlib_py.CRYPT_UNUSED) - try: - cryptlib_py.cryptCheckCert(c1, c2) - except: - return False - cryptlib_py.cryptDestroyCert(c1) - c1 = None - cryptlib_py.cryptDestroyCert(c2) - c2 = None - - #If the last certificate is one of the root certificates, we're - #done. - if self.x509List[-1].getFingerprint() in rootFingerprints: - return True - - #Otherwise, find a root certificate that the last certificate - #chains to, and validate them. - lastC = cryptlib_py.cryptImportCert(self.x509List[-1].writeBytes(), - cryptlib_py.CRYPT_UNUSED) - for rootCert in x509TrustList: - rootC = cryptlib_py.cryptImportCert(rootCert.writeBytes(), - cryptlib_py.CRYPT_UNUSED) - if self._checkChaining(lastC, rootC): - try: - cryptlib_py.cryptCheckCert(lastC, rootC) - return True - except: - return False - return False - finally: - if not (c1 is None): - cryptlib_py.cryptDestroyCert(c1) - if not (c2 is None): - cryptlib_py.cryptDestroyCert(c2) - if not (lastC is None): - cryptlib_py.cryptDestroyCert(lastC) - if not (rootC is None): - cryptlib_py.cryptDestroyCert(rootC) - - - - def _checkChaining(self, lastC, rootC): - import cryptlib_py - import array - def compareNames(name): - try: - length = cryptlib_py.cryptGetAttributeString(lastC, name, None) - lastName = array.array('B', [0] * length) - cryptlib_py.cryptGetAttributeString(lastC, name, lastName) - lastName = lastName.tostring() - except cryptlib_py.CryptException, e: - if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: - lastName = None - try: - length = cryptlib_py.cryptGetAttributeString(rootC, name, None) - rootName = array.array('B', [0] * length) - cryptlib_py.cryptGetAttributeString(rootC, name, rootName) - rootName = rootName.tostring() - except cryptlib_py.CryptException, e: - if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: - rootName = None - - return lastName == rootName - - cryptlib_py.cryptSetAttribute(lastC, - cryptlib_py.CRYPT_CERTINFO_ISSUERNAME, - cryptlib_py.CRYPT_UNUSED) - - if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME): - return False - if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME): - return False - if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME): - return False - if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME): - return False - if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME): - return False +"""Class representing an X.509 certificate chain.""" + +from utils import cryptomath + +class X509CertChain: + """This class represents a chain of X.509 certificates. + + @type x509List: list + @ivar x509List: A list of L{tlslite.X509.X509} instances, + starting with the end-entity certificate and with every + subsequent certificate certifying the previous. + """ + + def __init__(self, x509List=None): + """Create a new X509CertChain. + + @type x509List: list + @param x509List: A list of L{tlslite.X509.X509} instances, + starting with the end-entity certificate and with every + subsequent certificate certifying the previous. + """ + if x509List: + self.x509List = x509List + else: + self.x509List = [] + + def getNumCerts(self): + """Get the number of certificates in this chain. + + @rtype: int + """ + return len(self.x509List) + + def getEndEntityPublicKey(self): + """Get the public key from the end-entity certificate. + + @rtype: L{tlslite.utils.RSAKey.RSAKey} + """ + if self.getNumCerts() == 0: + raise AssertionError() + return self.x509List[0].publicKey + + def getFingerprint(self): + """Get the hex-encoded fingerprint of the end-entity certificate. + + @rtype: str + @return: A hex-encoded fingerprint. + """ + if self.getNumCerts() == 0: + raise AssertionError() + return self.x509List[0].getFingerprint() + + def getCommonName(self): + """Get the Subject's Common Name from the end-entity certificate. + + The cryptlib_py module must be installed in order to use this + function. + + @rtype: str or None + @return: The CN component of the certificate's subject DN, if + present. + """ + if self.getNumCerts() == 0: + raise AssertionError() + return self.x509List[0].getCommonName() + + def validate(self, x509TrustList): + """Check the validity of the certificate chain. + + This checks that every certificate in the chain validates with + the subsequent one, until some certificate validates with (or + is identical to) one of the passed-in root certificates. + + The cryptlib_py module must be installed in order to use this + function. + + @type x509TrustList: list of L{tlslite.X509.X509} + @param x509TrustList: A list of trusted root certificates. The + certificate chain must extend to one of these certificates to + be considered valid. + """ + + import cryptlib_py + c1 = None + c2 = None + lastC = None + rootC = None + + try: + rootFingerprints = [c.getFingerprint() for c in x509TrustList] + + #Check that every certificate in the chain validates with the + #next one + for cert1, cert2 in zip(self.x509List, self.x509List[1:]): + + #If we come upon a root certificate, we're done. + if cert1.getFingerprint() in rootFingerprints: + return True + + c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(), + cryptlib_py.CRYPT_UNUSED) + c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(), + cryptlib_py.CRYPT_UNUSED) + try: + cryptlib_py.cryptCheckCert(c1, c2) + except: + return False + cryptlib_py.cryptDestroyCert(c1) + c1 = None + cryptlib_py.cryptDestroyCert(c2) + c2 = None + + #If the last certificate is one of the root certificates, we're + #done. + if self.x509List[-1].getFingerprint() in rootFingerprints: + return True + + #Otherwise, find a root certificate that the last certificate + #chains to, and validate them. + lastC = cryptlib_py.cryptImportCert(self.x509List[-1].writeBytes(), + cryptlib_py.CRYPT_UNUSED) + for rootCert in x509TrustList: + rootC = cryptlib_py.cryptImportCert(rootCert.writeBytes(), + cryptlib_py.CRYPT_UNUSED) + if self._checkChaining(lastC, rootC): + try: + cryptlib_py.cryptCheckCert(lastC, rootC) + return True + except: + return False + return False + finally: + if not (c1 is None): + cryptlib_py.cryptDestroyCert(c1) + if not (c2 is None): + cryptlib_py.cryptDestroyCert(c2) + if not (lastC is None): + cryptlib_py.cryptDestroyCert(lastC) + if not (rootC is None): + cryptlib_py.cryptDestroyCert(rootC) + + + + def _checkChaining(self, lastC, rootC): + import cryptlib_py + import array + def compareNames(name): + try: + length = cryptlib_py.cryptGetAttributeString(lastC, name, None) + lastName = array.array('B', [0] * length) + cryptlib_py.cryptGetAttributeString(lastC, name, lastName) + lastName = lastName.tostring() + except cryptlib_py.CryptException, e: + if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: + lastName = None + try: + length = cryptlib_py.cryptGetAttributeString(rootC, name, None) + rootName = array.array('B', [0] * length) + cryptlib_py.cryptGetAttributeString(rootC, name, rootName) + rootName = rootName.tostring() + except cryptlib_py.CryptException, e: + if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND: + rootName = None + + return lastName == rootName + + cryptlib_py.cryptSetAttribute(lastC, + cryptlib_py.CRYPT_CERTINFO_ISSUERNAME, + cryptlib_py.CRYPT_UNUSED) + + if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME): + return False + if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME): + return False + if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME): + return False + if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME): + return False + if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME): + return False return True \ No newline at end of file diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/integration/AsyncStateMachine.py gtkrawgallery-0.9.9/src/gdata/tlslite/integration/AsyncStateMachine.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/integration/AsyncStateMachine.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/integration/AsyncStateMachine.py 2013-09-24 08:50:04.000000000 +0000 @@ -1,235 +1,235 @@ -""" -A state machine for using TLS Lite with asynchronous I/O. -""" - -class AsyncStateMachine: - """ - This is an abstract class that's used to integrate TLS Lite with - asyncore and Twisted. - - This class signals wantsReadsEvent() and wantsWriteEvent(). When - the underlying socket has become readable or writeable, the event - should be passed to this class by calling inReadEvent() or - inWriteEvent(). This class will then try to read or write through - the socket, and will update its state appropriately. - - This class will forward higher-level events to its subclass. For - example, when a complete TLS record has been received, - outReadEvent() will be called with the decrypted data. - """ - - def __init__(self): - self._clear() - - def _clear(self): - #These store the various asynchronous operations (i.e. - #generators). Only one of them, at most, is ever active at a - #time. - self.handshaker = None - self.closer = None - self.reader = None - self.writer = None - - #This stores the result from the last call to the - #currently active operation. If 0 it indicates that the - #operation wants to read, if 1 it indicates that the - #operation wants to write. If None, there is no active - #operation. - self.result = None - - def _checkAssert(self, maxActive=1): - #This checks that only one operation, at most, is - #active, and that self.result is set appropriately. - activeOps = 0 - if self.handshaker: - activeOps += 1 - if self.closer: - activeOps += 1 - if self.reader: - activeOps += 1 - if self.writer: - activeOps += 1 - - if self.result == None: - if activeOps != 0: - raise AssertionError() - elif self.result in (0,1): - if activeOps != 1: - raise AssertionError() - else: - raise AssertionError() - if activeOps > maxActive: - raise AssertionError() - - def wantsReadEvent(self): - """If the state machine wants to read. - - If an operation is active, this returns whether or not the - operation wants to read from the socket. If an operation is - not active, this returns None. - - @rtype: bool or None - @return: If the state machine wants to read. - """ - if self.result != None: - return self.result == 0 - return None - - def wantsWriteEvent(self): - """If the state machine wants to write. - - If an operation is active, this returns whether or not the - operation wants to write to the socket. If an operation is - not active, this returns None. - - @rtype: bool or None - @return: If the state machine wants to write. - """ - if self.result != None: - return self.result == 1 - return None - - def outConnectEvent(self): - """Called when a handshake operation completes. - - May be overridden in subclass. - """ - pass - - def outCloseEvent(self): - """Called when a close operation completes. - - May be overridden in subclass. - """ - pass - - def outReadEvent(self, readBuffer): - """Called when a read operation completes. - - May be overridden in subclass.""" - pass - - def outWriteEvent(self): - """Called when a write operation completes. - - May be overridden in subclass.""" - pass - - def inReadEvent(self): - """Tell the state machine it can read from the socket.""" - try: - self._checkAssert() - if self.handshaker: - self._doHandshakeOp() - elif self.closer: - self._doCloseOp() - elif self.reader: - self._doReadOp() - elif self.writer: - self._doWriteOp() - else: - self.reader = self.tlsConnection.readAsync(16384) - self._doReadOp() - except: - self._clear() - raise - - def inWriteEvent(self): - """Tell the state machine it can write to the socket.""" - try: - self._checkAssert() - if self.handshaker: - self._doHandshakeOp() - elif self.closer: - self._doCloseOp() - elif self.reader: - self._doReadOp() - elif self.writer: - self._doWriteOp() - else: - self.outWriteEvent() - except: - self._clear() - raise - - def _doHandshakeOp(self): - try: - self.result = self.handshaker.next() - except StopIteration: - self.handshaker = None - self.result = None - self.outConnectEvent() - - def _doCloseOp(self): - try: - self.result = self.closer.next() - except StopIteration: - self.closer = None - self.result = None - self.outCloseEvent() - - def _doReadOp(self): - self.result = self.reader.next() - if not self.result in (0,1): - readBuffer = self.result - self.reader = None - self.result = None - self.outReadEvent(readBuffer) - - def _doWriteOp(self): - try: - self.result = self.writer.next() - except StopIteration: - self.writer = None - self.result = None - - def setHandshakeOp(self, handshaker): - """Start a handshake operation. - - @type handshaker: generator - @param handshaker: A generator created by using one of the - asynchronous handshake functions (i.e. handshakeServerAsync, or - handshakeClientxxx(..., async=True). - """ - try: - self._checkAssert(0) - self.handshaker = handshaker - self._doHandshakeOp() - except: - self._clear() - raise - - def setServerHandshakeOp(self, **args): - """Start a handshake operation. - - The arguments passed to this function will be forwarded to - L{tlslite.TLSConnection.TLSConnection.handshakeServerAsync}. - """ - handshaker = self.tlsConnection.handshakeServerAsync(**args) - self.setHandshakeOp(handshaker) - - def setCloseOp(self): - """Start a close operation. - """ - try: - self._checkAssert(0) - self.closer = self.tlsConnection.closeAsync() - self._doCloseOp() - except: - self._clear() - raise - - def setWriteOp(self, writeBuffer): - """Start a write operation. - - @type writeBuffer: str - @param writeBuffer: The string to transmit. - """ - try: - self._checkAssert(0) - self.writer = self.tlsConnection.writeAsync(writeBuffer) - self._doWriteOp() - except: - self._clear() - raise - +""" +A state machine for using TLS Lite with asynchronous I/O. +""" + +class AsyncStateMachine: + """ + This is an abstract class that's used to integrate TLS Lite with + asyncore and Twisted. + + This class signals wantsReadsEvent() and wantsWriteEvent(). When + the underlying socket has become readable or writeable, the event + should be passed to this class by calling inReadEvent() or + inWriteEvent(). This class will then try to read or write through + the socket, and will update its state appropriately. + + This class will forward higher-level events to its subclass. For + example, when a complete TLS record has been received, + outReadEvent() will be called with the decrypted data. + """ + + def __init__(self): + self._clear() + + def _clear(self): + #These store the various asynchronous operations (i.e. + #generators). Only one of them, at most, is ever active at a + #time. + self.handshaker = None + self.closer = None + self.reader = None + self.writer = None + + #This stores the result from the last call to the + #currently active operation. If 0 it indicates that the + #operation wants to read, if 1 it indicates that the + #operation wants to write. If None, there is no active + #operation. + self.result = None + + def _checkAssert(self, maxActive=1): + #This checks that only one operation, at most, is + #active, and that self.result is set appropriately. + activeOps = 0 + if self.handshaker: + activeOps += 1 + if self.closer: + activeOps += 1 + if self.reader: + activeOps += 1 + if self.writer: + activeOps += 1 + + if self.result == None: + if activeOps != 0: + raise AssertionError() + elif self.result in (0,1): + if activeOps != 1: + raise AssertionError() + else: + raise AssertionError() + if activeOps > maxActive: + raise AssertionError() + + def wantsReadEvent(self): + """If the state machine wants to read. + + If an operation is active, this returns whether or not the + operation wants to read from the socket. If an operation is + not active, this returns None. + + @rtype: bool or None + @return: If the state machine wants to read. + """ + if self.result != None: + return self.result == 0 + return None + + def wantsWriteEvent(self): + """If the state machine wants to write. + + If an operation is active, this returns whether or not the + operation wants to write to the socket. If an operation is + not active, this returns None. + + @rtype: bool or None + @return: If the state machine wants to write. + """ + if self.result != None: + return self.result == 1 + return None + + def outConnectEvent(self): + """Called when a handshake operation completes. + + May be overridden in subclass. + """ + pass + + def outCloseEvent(self): + """Called when a close operation completes. + + May be overridden in subclass. + """ + pass + + def outReadEvent(self, readBuffer): + """Called when a read operation completes. + + May be overridden in subclass.""" + pass + + def outWriteEvent(self): + """Called when a write operation completes. + + May be overridden in subclass.""" + pass + + def inReadEvent(self): + """Tell the state machine it can read from the socket.""" + try: + self._checkAssert() + if self.handshaker: + self._doHandshakeOp() + elif self.closer: + self._doCloseOp() + elif self.reader: + self._doReadOp() + elif self.writer: + self._doWriteOp() + else: + self.reader = self.tlsConnection.readAsync(16384) + self._doReadOp() + except: + self._clear() + raise + + def inWriteEvent(self): + """Tell the state machine it can write to the socket.""" + try: + self._checkAssert() + if self.handshaker: + self._doHandshakeOp() + elif self.closer: + self._doCloseOp() + elif self.reader: + self._doReadOp() + elif self.writer: + self._doWriteOp() + else: + self.outWriteEvent() + except: + self._clear() + raise + + def _doHandshakeOp(self): + try: + self.result = self.handshaker.next() + except StopIteration: + self.handshaker = None + self.result = None + self.outConnectEvent() + + def _doCloseOp(self): + try: + self.result = self.closer.next() + except StopIteration: + self.closer = None + self.result = None + self.outCloseEvent() + + def _doReadOp(self): + self.result = self.reader.next() + if not self.result in (0,1): + readBuffer = self.result + self.reader = None + self.result = None + self.outReadEvent(readBuffer) + + def _doWriteOp(self): + try: + self.result = self.writer.next() + except StopIteration: + self.writer = None + self.result = None + + def setHandshakeOp(self, handshaker): + """Start a handshake operation. + + @type handshaker: generator + @param handshaker: A generator created by using one of the + asynchronous handshake functions (i.e. handshakeServerAsync, or + handshakeClientxxx(..., async=True). + """ + try: + self._checkAssert(0) + self.handshaker = handshaker + self._doHandshakeOp() + except: + self._clear() + raise + + def setServerHandshakeOp(self, **args): + """Start a handshake operation. + + The arguments passed to this function will be forwarded to + L{tlslite.TLSConnection.TLSConnection.handshakeServerAsync}. + """ + handshaker = self.tlsConnection.handshakeServerAsync(**args) + self.setHandshakeOp(handshaker) + + def setCloseOp(self): + """Start a close operation. + """ + try: + self._checkAssert(0) + self.closer = self.tlsConnection.closeAsync() + self._doCloseOp() + except: + self._clear() + raise + + def setWriteOp(self, writeBuffer): + """Start a write operation. + + @type writeBuffer: str + @param writeBuffer: The string to transmit. + """ + try: + self._checkAssert(0) + self.writer = self.tlsConnection.writeAsync(writeBuffer) + self._doWriteOp() + except: + self._clear() + raise + diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/integration/ClientHelper.py gtkrawgallery-0.9.9/src/gdata/tlslite/integration/ClientHelper.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/integration/ClientHelper.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/integration/ClientHelper.py 2013-09-24 08:50:04.000000000 +0000 @@ -1,163 +1,163 @@ -""" -A helper class for using TLS Lite with stdlib clients -(httplib, xmlrpclib, imaplib, poplib). -""" - -from gdata.tlslite.Checker import Checker - -class ClientHelper: - """This is a helper class used to integrate TLS Lite with various - TLS clients (e.g. poplib, smtplib, httplib, etc.)""" - - def __init__(self, - username=None, password=None, sharedKey=None, - certChain=None, privateKey=None, - cryptoID=None, protocol=None, - x509Fingerprint=None, - x509TrustList=None, x509CommonName=None, - settings = None): - """ - For client authentication, use one of these argument - combinations: - - username, password (SRP) - - username, sharedKey (shared-key) - - certChain, privateKey (certificate) - - For server authentication, you can either rely on the - implicit mutual authentication performed by SRP or - shared-keys, or you can do certificate-based server - authentication with one of these argument combinations: - - cryptoID[, protocol] (requires cryptoIDlib) - - x509Fingerprint - - x509TrustList[, x509CommonName] (requires cryptlib_py) - - Certificate-based server authentication is compatible with - SRP or certificate-based client authentication. It is - not compatible with shared-keys. - - The constructor does not perform the TLS handshake itself, but - simply stores these arguments for later. The handshake is - performed only when this class needs to connect with the - server. Then you should be prepared to handle TLS-specific - exceptions. See the client handshake functions in - L{tlslite.TLSConnection.TLSConnection} for details on which - exceptions might be raised. - - @type username: str - @param username: SRP or shared-key username. Requires the - 'password' or 'sharedKey' argument. - - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. - - @type sharedKey: str - @param sharedKey: Shared key for mutual authentication. - Requires the 'username' argument. - - @type certChain: L{tlslite.X509CertChain.X509CertChain} or - L{cryptoIDlib.CertChain.CertChain} - @param certChain: Certificate chain for client authentication. - Requires the 'privateKey' argument. Excludes the SRP or - shared-key related arguments. - - @type privateKey: L{tlslite.utils.RSAKey.RSAKey} - @param privateKey: Private key for client authentication. - Requires the 'certChain' argument. Excludes the SRP or - shared-key related arguments. - - @type cryptoID: str - @param cryptoID: cryptoID for server authentication. Mutually - exclusive with the 'x509...' arguments. - - @type protocol: str - @param protocol: cryptoID protocol URI for server - authentication. Requires the 'cryptoID' argument. - - @type x509Fingerprint: str - @param x509Fingerprint: Hex-encoded X.509 fingerprint for - server authentication. Mutually exclusive with the 'cryptoID' - and 'x509TrustList' arguments. - - @type x509TrustList: list of L{tlslite.X509.X509} - @param x509TrustList: A list of trusted root certificates. The - other party must present a certificate chain which extends to - one of these root certificates. The cryptlib_py module must be - installed to use this parameter. Mutually exclusive with the - 'cryptoID' and 'x509Fingerprint' arguments. - - @type x509CommonName: str - @param x509CommonName: The end-entity certificate's 'CN' field - must match this value. For a web server, this is typically a - server name such as 'www.amazon.com'. Mutually exclusive with - the 'cryptoID' and 'x509Fingerprint' arguments. Requires the - 'x509TrustList' argument. - - @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. - """ - - self.username = None - self.password = None - self.sharedKey = None - self.certChain = None - self.privateKey = None - self.checker = None - - #SRP Authentication - if username and password and not \ - (sharedKey or certChain or privateKey): - self.username = username - self.password = password - - #Shared Key Authentication - elif username and sharedKey and not \ - (password or certChain or privateKey): - self.username = username - self.sharedKey = sharedKey - - #Certificate Chain Authentication - elif certChain and privateKey and not \ - (username or password or sharedKey): - self.certChain = certChain - self.privateKey = privateKey - - #No Authentication - elif not password and not username and not \ - sharedKey and not certChain and not privateKey: - pass - - else: - raise ValueError("Bad parameters") - - #Authenticate the server based on its cryptoID or fingerprint - if sharedKey and (cryptoID or protocol or x509Fingerprint): - raise ValueError("Can't use shared keys with other forms of"\ - "authentication") - - self.checker = Checker(cryptoID, protocol, x509Fingerprint, - x509TrustList, x509CommonName) - self.settings = settings - - self.tlsSession = None - - def _handshake(self, tlsConnection): - if self.username and self.password: - tlsConnection.handshakeClientSRP(username=self.username, - password=self.password, - checker=self.checker, - settings=self.settings, - session=self.tlsSession) - elif self.username and self.sharedKey: - tlsConnection.handshakeClientSharedKey(username=self.username, - sharedKey=self.sharedKey, - settings=self.settings) - else: - tlsConnection.handshakeClientCert(certChain=self.certChain, - privateKey=self.privateKey, - checker=self.checker, - settings=self.settings, - session=self.tlsSession) - self.tlsSession = tlsConnection.session +""" +A helper class for using TLS Lite with stdlib clients +(httplib, xmlrpclib, imaplib, poplib). +""" + +from gdata.tlslite.Checker import Checker + +class ClientHelper: + """This is a helper class used to integrate TLS Lite with various + TLS clients (e.g. poplib, smtplib, httplib, etc.)""" + + def __init__(self, + username=None, password=None, sharedKey=None, + certChain=None, privateKey=None, + cryptoID=None, protocol=None, + x509Fingerprint=None, + x509TrustList=None, x509CommonName=None, + settings = None): + """ + For client authentication, use one of these argument + combinations: + - username, password (SRP) + - username, sharedKey (shared-key) + - certChain, privateKey (certificate) + + For server authentication, you can either rely on the + implicit mutual authentication performed by SRP or + shared-keys, or you can do certificate-based server + authentication with one of these argument combinations: + - cryptoID[, protocol] (requires cryptoIDlib) + - x509Fingerprint + - x509TrustList[, x509CommonName] (requires cryptlib_py) + + Certificate-based server authentication is compatible with + SRP or certificate-based client authentication. It is + not compatible with shared-keys. + + The constructor does not perform the TLS handshake itself, but + simply stores these arguments for later. The handshake is + performed only when this class needs to connect with the + server. Then you should be prepared to handle TLS-specific + exceptions. See the client handshake functions in + L{tlslite.TLSConnection.TLSConnection} for details on which + exceptions might be raised. + + @type username: str + @param username: SRP or shared-key username. Requires the + 'password' or 'sharedKey' argument. + + @type password: str + @param password: SRP password for mutual authentication. + Requires the 'username' argument. + + @type sharedKey: str + @param sharedKey: Shared key for mutual authentication. + Requires the 'username' argument. + + @type certChain: L{tlslite.X509CertChain.X509CertChain} or + L{cryptoIDlib.CertChain.CertChain} + @param certChain: Certificate chain for client authentication. + Requires the 'privateKey' argument. Excludes the SRP or + shared-key related arguments. + + @type privateKey: L{tlslite.utils.RSAKey.RSAKey} + @param privateKey: Private key for client authentication. + Requires the 'certChain' argument. Excludes the SRP or + shared-key related arguments. + + @type cryptoID: str + @param cryptoID: cryptoID for server authentication. Mutually + exclusive with the 'x509...' arguments. + + @type protocol: str + @param protocol: cryptoID protocol URI for server + authentication. Requires the 'cryptoID' argument. + + @type x509Fingerprint: str + @param x509Fingerprint: Hex-encoded X.509 fingerprint for + server authentication. Mutually exclusive with the 'cryptoID' + and 'x509TrustList' arguments. + + @type x509TrustList: list of L{tlslite.X509.X509} + @param x509TrustList: A list of trusted root certificates. The + other party must present a certificate chain which extends to + one of these root certificates. The cryptlib_py module must be + installed to use this parameter. Mutually exclusive with the + 'cryptoID' and 'x509Fingerprint' arguments. + + @type x509CommonName: str + @param x509CommonName: The end-entity certificate's 'CN' field + must match this value. For a web server, this is typically a + server name such as 'www.amazon.com'. Mutually exclusive with + the 'cryptoID' and 'x509Fingerprint' arguments. Requires the + 'x509TrustList' argument. + + @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} + @param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. + """ + + self.username = None + self.password = None + self.sharedKey = None + self.certChain = None + self.privateKey = None + self.checker = None + + #SRP Authentication + if username and password and not \ + (sharedKey or certChain or privateKey): + self.username = username + self.password = password + + #Shared Key Authentication + elif username and sharedKey and not \ + (password or certChain or privateKey): + self.username = username + self.sharedKey = sharedKey + + #Certificate Chain Authentication + elif certChain and privateKey and not \ + (username or password or sharedKey): + self.certChain = certChain + self.privateKey = privateKey + + #No Authentication + elif not password and not username and not \ + sharedKey and not certChain and not privateKey: + pass + + else: + raise ValueError("Bad parameters") + + #Authenticate the server based on its cryptoID or fingerprint + if sharedKey and (cryptoID or protocol or x509Fingerprint): + raise ValueError("Can't use shared keys with other forms of"\ + "authentication") + + self.checker = Checker(cryptoID, protocol, x509Fingerprint, + x509TrustList, x509CommonName) + self.settings = settings + + self.tlsSession = None + + def _handshake(self, tlsConnection): + if self.username and self.password: + tlsConnection.handshakeClientSRP(username=self.username, + password=self.password, + checker=self.checker, + settings=self.settings, + session=self.tlsSession) + elif self.username and self.sharedKey: + tlsConnection.handshakeClientSharedKey(username=self.username, + sharedKey=self.sharedKey, + settings=self.settings) + else: + tlsConnection.handshakeClientCert(certChain=self.certChain, + privateKey=self.privateKey, + checker=self.checker, + settings=self.settings, + session=self.tlsSession) + self.tlsSession = tlsConnection.session diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/integration/IntegrationHelper.py gtkrawgallery-0.9.9/src/gdata/tlslite/integration/IntegrationHelper.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/integration/IntegrationHelper.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/integration/IntegrationHelper.py 2013-09-24 08:50:04.000000000 +0000 @@ -1,52 +1,52 @@ - -class IntegrationHelper: - - def __init__(self, - username=None, password=None, sharedKey=None, - certChain=None, privateKey=None, - cryptoID=None, protocol=None, - x509Fingerprint=None, - x509TrustList=None, x509CommonName=None, - settings = None): - - self.username = None - self.password = None - self.sharedKey = None - self.certChain = None - self.privateKey = None - self.checker = None - - #SRP Authentication - if username and password and not \ - (sharedKey or certChain or privateKey): - self.username = username - self.password = password - - #Shared Key Authentication - elif username and sharedKey and not \ - (password or certChain or privateKey): - self.username = username - self.sharedKey = sharedKey - - #Certificate Chain Authentication - elif certChain and privateKey and not \ - (username or password or sharedKey): - self.certChain = certChain - self.privateKey = privateKey - - #No Authentication - elif not password and not username and not \ - sharedKey and not certChain and not privateKey: - pass - - else: - raise ValueError("Bad parameters") - - #Authenticate the server based on its cryptoID or fingerprint - if sharedKey and (cryptoID or protocol or x509Fingerprint): - raise ValueError("Can't use shared keys with other forms of"\ - "authentication") - - self.checker = Checker(cryptoID, protocol, x509Fingerprint, - x509TrustList, x509CommonName) + +class IntegrationHelper: + + def __init__(self, + username=None, password=None, sharedKey=None, + certChain=None, privateKey=None, + cryptoID=None, protocol=None, + x509Fingerprint=None, + x509TrustList=None, x509CommonName=None, + settings = None): + + self.username = None + self.password = None + self.sharedKey = None + self.certChain = None + self.privateKey = None + self.checker = None + + #SRP Authentication + if username and password and not \ + (sharedKey or certChain or privateKey): + self.username = username + self.password = password + + #Shared Key Authentication + elif username and sharedKey and not \ + (password or certChain or privateKey): + self.username = username + self.sharedKey = sharedKey + + #Certificate Chain Authentication + elif certChain and privateKey and not \ + (username or password or sharedKey): + self.certChain = certChain + self.privateKey = privateKey + + #No Authentication + elif not password and not username and not \ + sharedKey and not certChain and not privateKey: + pass + + else: + raise ValueError("Bad parameters") + + #Authenticate the server based on its cryptoID or fingerprint + if sharedKey and (cryptoID or protocol or x509Fingerprint): + raise ValueError("Can't use shared keys with other forms of"\ + "authentication") + + self.checker = Checker(cryptoID, protocol, x509Fingerprint, + x509TrustList, x509CommonName) self.settings = settings \ No newline at end of file diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py gtkrawgallery-0.9.9/src/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py 2013-09-24 08:50:04.000000000 +0000 @@ -1,139 +1,139 @@ -"""TLS Lite + asyncore.""" - - -import asyncore -from gdata.tlslite.TLSConnection import TLSConnection -from AsyncStateMachine import AsyncStateMachine - - -class TLSAsyncDispatcherMixIn(AsyncStateMachine): - """This class can be "mixed in" with an - L{asyncore.dispatcher} to add TLS support. - - This class essentially sits between the dispatcher and the select - loop, intercepting events and only calling the dispatcher when - applicable. - - In the case of handle_read(), a read operation will be activated, - and when it completes, the bytes will be placed in a buffer where - the dispatcher can retrieve them by calling recv(), and the - dispatcher's handle_read() will be called. - - In the case of handle_write(), the dispatcher's handle_write() will - be called, and when it calls send(), a write operation will be - activated. - - To use this class, you must combine it with an asyncore.dispatcher, - and pass in a handshake operation with setServerHandshakeOp(). - - Below is an example of using this class with medusa. This class is - mixed in with http_channel to create http_tls_channel. Note: - 1. the mix-in is listed first in the inheritance list - - 2. the input buffer size must be at least 16K, otherwise the - dispatcher might not read all the bytes from the TLS layer, - leaving some bytes in limbo. - - 3. IE seems to have a problem receiving a whole HTTP response in a - single TLS record, so HTML pages containing '\\r\\n\\r\\n' won't - be displayed on IE. - - Add the following text into 'start_medusa.py', in the 'HTTP Server' - section:: - - from tlslite.api import * - s = open("./serverX509Cert.pem").read() - x509 = X509() - x509.parse(s) - certChain = X509CertChain([x509]) - - s = open("./serverX509Key.pem").read() - privateKey = parsePEMKey(s, private=True) - - class http_tls_channel(TLSAsyncDispatcherMixIn, - http_server.http_channel): - ac_in_buffer_size = 16384 - - def __init__ (self, server, conn, addr): - http_server.http_channel.__init__(self, server, conn, addr) - TLSAsyncDispatcherMixIn.__init__(self, conn) - self.tlsConnection.ignoreAbruptClose = True - self.setServerHandshakeOp(certChain=certChain, - privateKey=privateKey) - - hs.channel_class = http_tls_channel - - If the TLS layer raises an exception, the exception will be caught - in asyncore.dispatcher, which will call close() on this class. The - TLS layer always closes the TLS connection before raising an - exception, so the close operation will complete right away, causing - asyncore.dispatcher.close() to be called, which closes the socket - and removes this instance from the asyncore loop. - - """ - - - def __init__(self, sock=None): - AsyncStateMachine.__init__(self) - - if sock: - self.tlsConnection = TLSConnection(sock) - - #Calculate the sibling I'm being mixed in with. - #This is necessary since we override functions - #like readable(), handle_read(), etc., but we - #also want to call the sibling's versions. - for cl in self.__class__.__bases__: - if cl != TLSAsyncDispatcherMixIn and cl != AsyncStateMachine: - self.siblingClass = cl - break - else: - raise AssertionError() - - def readable(self): - result = self.wantsReadEvent() - if result != None: - return result - return self.siblingClass.readable(self) - - def writable(self): - result = self.wantsWriteEvent() - if result != None: - return result - return self.siblingClass.writable(self) - - def handle_read(self): - self.inReadEvent() - - def handle_write(self): - self.inWriteEvent() - - def outConnectEvent(self): - self.siblingClass.handle_connect(self) - - def outCloseEvent(self): - asyncore.dispatcher.close(self) - - def outReadEvent(self, readBuffer): - self.readBuffer = readBuffer - self.siblingClass.handle_read(self) - - def outWriteEvent(self): - self.siblingClass.handle_write(self) - - def recv(self, bufferSize=16384): - if bufferSize < 16384 or self.readBuffer == None: - raise AssertionError() - returnValue = self.readBuffer - self.readBuffer = None - return returnValue - - def send(self, writeBuffer): - self.setWriteOp(writeBuffer) - return len(writeBuffer) - - def close(self): - if hasattr(self, "tlsConnection"): - self.setCloseOp() - else: - asyncore.dispatcher.close(self) +"""TLS Lite + asyncore.""" + + +import asyncore +from gdata.tlslite.TLSConnection import TLSConnection +from AsyncStateMachine import AsyncStateMachine + + +class TLSAsyncDispatcherMixIn(AsyncStateMachine): + """This class can be "mixed in" with an + L{asyncore.dispatcher} to add TLS support. + + This class essentially sits between the dispatcher and the select + loop, intercepting events and only calling the dispatcher when + applicable. + + In the case of handle_read(), a read operation will be activated, + and when it completes, the bytes will be placed in a buffer where + the dispatcher can retrieve them by calling recv(), and the + dispatcher's handle_read() will be called. + + In the case of handle_write(), the dispatcher's handle_write() will + be called, and when it calls send(), a write operation will be + activated. + + To use this class, you must combine it with an asyncore.dispatcher, + and pass in a handshake operation with setServerHandshakeOp(). + + Below is an example of using this class with medusa. This class is + mixed in with http_channel to create http_tls_channel. Note: + 1. the mix-in is listed first in the inheritance list + + 2. the input buffer size must be at least 16K, otherwise the + dispatcher might not read all the bytes from the TLS layer, + leaving some bytes in limbo. + + 3. IE seems to have a problem receiving a whole HTTP response in a + single TLS record, so HTML pages containing '\\r\\n\\r\\n' won't + be displayed on IE. + + Add the following text into 'start_medusa.py', in the 'HTTP Server' + section:: + + from tlslite.api import * + s = open("./serverX509Cert.pem").read() + x509 = X509() + x509.parse(s) + certChain = X509CertChain([x509]) + + s = open("./serverX509Key.pem").read() + privateKey = parsePEMKey(s, private=True) + + class http_tls_channel(TLSAsyncDispatcherMixIn, + http_server.http_channel): + ac_in_buffer_size = 16384 + + def __init__ (self, server, conn, addr): + http_server.http_channel.__init__(self, server, conn, addr) + TLSAsyncDispatcherMixIn.__init__(self, conn) + self.tlsConnection.ignoreAbruptClose = True + self.setServerHandshakeOp(certChain=certChain, + privateKey=privateKey) + + hs.channel_class = http_tls_channel + + If the TLS layer raises an exception, the exception will be caught + in asyncore.dispatcher, which will call close() on this class. The + TLS layer always closes the TLS connection before raising an + exception, so the close operation will complete right away, causing + asyncore.dispatcher.close() to be called, which closes the socket + and removes this instance from the asyncore loop. + + """ + + + def __init__(self, sock=None): + AsyncStateMachine.__init__(self) + + if sock: + self.tlsConnection = TLSConnection(sock) + + #Calculate the sibling I'm being mixed in with. + #This is necessary since we override functions + #like readable(), handle_read(), etc., but we + #also want to call the sibling's versions. + for cl in self.__class__.__bases__: + if cl != TLSAsyncDispatcherMixIn and cl != AsyncStateMachine: + self.siblingClass = cl + break + else: + raise AssertionError() + + def readable(self): + result = self.wantsReadEvent() + if result != None: + return result + return self.siblingClass.readable(self) + + def writable(self): + result = self.wantsWriteEvent() + if result != None: + return result + return self.siblingClass.writable(self) + + def handle_read(self): + self.inReadEvent() + + def handle_write(self): + self.inWriteEvent() + + def outConnectEvent(self): + self.siblingClass.handle_connect(self) + + def outCloseEvent(self): + asyncore.dispatcher.close(self) + + def outReadEvent(self, readBuffer): + self.readBuffer = readBuffer + self.siblingClass.handle_read(self) + + def outWriteEvent(self): + self.siblingClass.handle_write(self) + + def recv(self, bufferSize=16384): + if bufferSize < 16384 or self.readBuffer == None: + raise AssertionError() + returnValue = self.readBuffer + self.readBuffer = None + return returnValue + + def send(self, writeBuffer): + self.setWriteOp(writeBuffer) + return len(writeBuffer) + + def close(self): + if hasattr(self, "tlsConnection"): + self.setCloseOp() + else: + asyncore.dispatcher.close(self) diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py gtkrawgallery-0.9.9/src/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py 2013-09-24 08:50:04.000000000 +0000 @@ -1,196 +1,196 @@ -"""TLS Lite + Twisted.""" - -from twisted.protocols.policies import ProtocolWrapper, WrappingFactory -from twisted.python.failure import Failure - -from AsyncStateMachine import AsyncStateMachine -from gdata.tlslite.TLSConnection import TLSConnection -from gdata.tlslite.errors import * - -import socket -import errno - - -#The TLSConnection is created around a "fake socket" that -#plugs it into the underlying Twisted transport -class _FakeSocket: - def __init__(self, wrapper): - self.wrapper = wrapper - self.data = "" - - def send(self, data): - ProtocolWrapper.write(self.wrapper, data) - return len(data) - - def recv(self, numBytes): - if self.data == "": - raise socket.error, (errno.EWOULDBLOCK, "") - returnData = self.data[:numBytes] - self.data = self.data[numBytes:] - return returnData - -class TLSTwistedProtocolWrapper(ProtocolWrapper, AsyncStateMachine): - """This class can wrap Twisted protocols to add TLS support. - - Below is a complete example of using TLS Lite with a Twisted echo - server. - - There are two server implementations below. Echo is the original - protocol, which is oblivious to TLS. Echo1 subclasses Echo and - negotiates TLS when the client connects. Echo2 subclasses Echo and - negotiates TLS when the client sends "STARTTLS":: - - from twisted.internet.protocol import Protocol, Factory - from twisted.internet import reactor - from twisted.protocols.policies import WrappingFactory - from twisted.protocols.basic import LineReceiver - from twisted.python import log - from twisted.python.failure import Failure - import sys - from tlslite.api import * - - s = open("./serverX509Cert.pem").read() - x509 = X509() - x509.parse(s) - certChain = X509CertChain([x509]) - - s = open("./serverX509Key.pem").read() - privateKey = parsePEMKey(s, private=True) - - verifierDB = VerifierDB("verifierDB") - verifierDB.open() - - class Echo(LineReceiver): - def connectionMade(self): - self.transport.write("Welcome to the echo server!\\r\\n") - - def lineReceived(self, line): - self.transport.write(line + "\\r\\n") - - class Echo1(Echo): - def connectionMade(self): - if not self.transport.tlsStarted: - self.transport.setServerHandshakeOp(certChain=certChain, - privateKey=privateKey, - verifierDB=verifierDB) - else: - Echo.connectionMade(self) - - def connectionLost(self, reason): - pass #Handle any TLS exceptions here - - class Echo2(Echo): - def lineReceived(self, data): - if data == "STARTTLS": - self.transport.setServerHandshakeOp(certChain=certChain, - privateKey=privateKey, - verifierDB=verifierDB) - else: - Echo.lineReceived(self, data) - - def connectionLost(self, reason): - pass #Handle any TLS exceptions here - - factory = Factory() - factory.protocol = Echo1 - #factory.protocol = Echo2 - - wrappingFactory = WrappingFactory(factory) - wrappingFactory.protocol = TLSTwistedProtocolWrapper - - log.startLogging(sys.stdout) - reactor.listenTCP(1079, wrappingFactory) - reactor.run() - - This class works as follows: - - Data comes in and is given to the AsyncStateMachine for handling. - AsyncStateMachine will forward events to this class, and we'll - pass them on to the ProtocolHandler, which will proxy them to the - wrapped protocol. The wrapped protocol may then call back into - this class, and these calls will be proxied into the - AsyncStateMachine. - - The call graph looks like this: - - self.dataReceived - - AsyncStateMachine.inReadEvent - - self.out(Connect|Close|Read)Event - - ProtocolWrapper.(connectionMade|loseConnection|dataReceived) - - self.(loseConnection|write|writeSequence) - - AsyncStateMachine.(setCloseOp|setWriteOp) - """ - - #WARNING: IF YOU COPY-AND-PASTE THE ABOVE CODE, BE SURE TO REMOVE - #THE EXTRA ESCAPING AROUND "\\r\\n" - - def __init__(self, factory, wrappedProtocol): - ProtocolWrapper.__init__(self, factory, wrappedProtocol) - AsyncStateMachine.__init__(self) - self.fakeSocket = _FakeSocket(self) - self.tlsConnection = TLSConnection(self.fakeSocket) - self.tlsStarted = False - self.connectionLostCalled = False - - def connectionMade(self): - try: - ProtocolWrapper.connectionMade(self) - except TLSError, e: - self.connectionLost(Failure(e)) - ProtocolWrapper.loseConnection(self) - - def dataReceived(self, data): - try: - if not self.tlsStarted: - ProtocolWrapper.dataReceived(self, data) - else: - self.fakeSocket.data += data - while self.fakeSocket.data: - AsyncStateMachine.inReadEvent(self) - except TLSError, e: - self.connectionLost(Failure(e)) - ProtocolWrapper.loseConnection(self) - - def connectionLost(self, reason): - if not self.connectionLostCalled: - ProtocolWrapper.connectionLost(self, reason) - self.connectionLostCalled = True - - - def outConnectEvent(self): - ProtocolWrapper.connectionMade(self) - - def outCloseEvent(self): - ProtocolWrapper.loseConnection(self) - - def outReadEvent(self, data): - if data == "": - ProtocolWrapper.loseConnection(self) - else: - ProtocolWrapper.dataReceived(self, data) - - - def setServerHandshakeOp(self, **args): - self.tlsStarted = True - AsyncStateMachine.setServerHandshakeOp(self, **args) - - def loseConnection(self): - if not self.tlsStarted: - ProtocolWrapper.loseConnection(self) - else: - AsyncStateMachine.setCloseOp(self) - - def write(self, data): - if not self.tlsStarted: - ProtocolWrapper.write(self, data) - else: - #Because of the FakeSocket, write operations are guaranteed to - #terminate immediately. - AsyncStateMachine.setWriteOp(self, data) - - def writeSequence(self, seq): - if not self.tlsStarted: - ProtocolWrapper.writeSequence(self, seq) - else: - #Because of the FakeSocket, write operations are guaranteed to - #terminate immediately. - AsyncStateMachine.setWriteOp(self, "".join(seq)) +"""TLS Lite + Twisted.""" + +from twisted.protocols.policies import ProtocolWrapper, WrappingFactory +from twisted.python.failure import Failure + +from AsyncStateMachine import AsyncStateMachine +from gdata.tlslite.TLSConnection import TLSConnection +from gdata.tlslite.errors import * + +import socket +import errno + + +#The TLSConnection is created around a "fake socket" that +#plugs it into the underlying Twisted transport +class _FakeSocket: + def __init__(self, wrapper): + self.wrapper = wrapper + self.data = "" + + def send(self, data): + ProtocolWrapper.write(self.wrapper, data) + return len(data) + + def recv(self, numBytes): + if self.data == "": + raise socket.error, (errno.EWOULDBLOCK, "") + returnData = self.data[:numBytes] + self.data = self.data[numBytes:] + return returnData + +class TLSTwistedProtocolWrapper(ProtocolWrapper, AsyncStateMachine): + """This class can wrap Twisted protocols to add TLS support. + + Below is a complete example of using TLS Lite with a Twisted echo + server. + + There are two server implementations below. Echo is the original + protocol, which is oblivious to TLS. Echo1 subclasses Echo and + negotiates TLS when the client connects. Echo2 subclasses Echo and + negotiates TLS when the client sends "STARTTLS":: + + from twisted.internet.protocol import Protocol, Factory + from twisted.internet import reactor + from twisted.protocols.policies import WrappingFactory + from twisted.protocols.basic import LineReceiver + from twisted.python import log + from twisted.python.failure import Failure + import sys + from tlslite.api import * + + s = open("./serverX509Cert.pem").read() + x509 = X509() + x509.parse(s) + certChain = X509CertChain([x509]) + + s = open("./serverX509Key.pem").read() + privateKey = parsePEMKey(s, private=True) + + verifierDB = VerifierDB("verifierDB") + verifierDB.open() + + class Echo(LineReceiver): + def connectionMade(self): + self.transport.write("Welcome to the echo server!\\r\\n") + + def lineReceived(self, line): + self.transport.write(line + "\\r\\n") + + class Echo1(Echo): + def connectionMade(self): + if not self.transport.tlsStarted: + self.transport.setServerHandshakeOp(certChain=certChain, + privateKey=privateKey, + verifierDB=verifierDB) + else: + Echo.connectionMade(self) + + def connectionLost(self, reason): + pass #Handle any TLS exceptions here + + class Echo2(Echo): + def lineReceived(self, data): + if data == "STARTTLS": + self.transport.setServerHandshakeOp(certChain=certChain, + privateKey=privateKey, + verifierDB=verifierDB) + else: + Echo.lineReceived(self, data) + + def connectionLost(self, reason): + pass #Handle any TLS exceptions here + + factory = Factory() + factory.protocol = Echo1 + #factory.protocol = Echo2 + + wrappingFactory = WrappingFactory(factory) + wrappingFactory.protocol = TLSTwistedProtocolWrapper + + log.startLogging(sys.stdout) + reactor.listenTCP(1079, wrappingFactory) + reactor.run() + + This class works as follows: + + Data comes in and is given to the AsyncStateMachine for handling. + AsyncStateMachine will forward events to this class, and we'll + pass them on to the ProtocolHandler, which will proxy them to the + wrapped protocol. The wrapped protocol may then call back into + this class, and these calls will be proxied into the + AsyncStateMachine. + + The call graph looks like this: + - self.dataReceived + - AsyncStateMachine.inReadEvent + - self.out(Connect|Close|Read)Event + - ProtocolWrapper.(connectionMade|loseConnection|dataReceived) + - self.(loseConnection|write|writeSequence) + - AsyncStateMachine.(setCloseOp|setWriteOp) + """ + + #WARNING: IF YOU COPY-AND-PASTE THE ABOVE CODE, BE SURE TO REMOVE + #THE EXTRA ESCAPING AROUND "\\r\\n" + + def __init__(self, factory, wrappedProtocol): + ProtocolWrapper.__init__(self, factory, wrappedProtocol) + AsyncStateMachine.__init__(self) + self.fakeSocket = _FakeSocket(self) + self.tlsConnection = TLSConnection(self.fakeSocket) + self.tlsStarted = False + self.connectionLostCalled = False + + def connectionMade(self): + try: + ProtocolWrapper.connectionMade(self) + except TLSError, e: + self.connectionLost(Failure(e)) + ProtocolWrapper.loseConnection(self) + + def dataReceived(self, data): + try: + if not self.tlsStarted: + ProtocolWrapper.dataReceived(self, data) + else: + self.fakeSocket.data += data + while self.fakeSocket.data: + AsyncStateMachine.inReadEvent(self) + except TLSError, e: + self.connectionLost(Failure(e)) + ProtocolWrapper.loseConnection(self) + + def connectionLost(self, reason): + if not self.connectionLostCalled: + ProtocolWrapper.connectionLost(self, reason) + self.connectionLostCalled = True + + + def outConnectEvent(self): + ProtocolWrapper.connectionMade(self) + + def outCloseEvent(self): + ProtocolWrapper.loseConnection(self) + + def outReadEvent(self, data): + if data == "": + ProtocolWrapper.loseConnection(self) + else: + ProtocolWrapper.dataReceived(self, data) + + + def setServerHandshakeOp(self, **args): + self.tlsStarted = True + AsyncStateMachine.setServerHandshakeOp(self, **args) + + def loseConnection(self): + if not self.tlsStarted: + ProtocolWrapper.loseConnection(self) + else: + AsyncStateMachine.setCloseOp(self) + + def write(self, data): + if not self.tlsStarted: + ProtocolWrapper.write(self, data) + else: + #Because of the FakeSocket, write operations are guaranteed to + #terminate immediately. + AsyncStateMachine.setWriteOp(self, data) + + def writeSequence(self, seq): + if not self.tlsStarted: + ProtocolWrapper.writeSequence(self, seq) + else: + #Because of the FakeSocket, write operations are guaranteed to + #terminate immediately. + AsyncStateMachine.setWriteOp(self, "".join(seq)) diff -Nru gtkrawgallery-0.9.8/src/gdata/tlslite/utils/Python_RSAKey.py gtkrawgallery-0.9.9/src/gdata/tlslite/utils/Python_RSAKey.py --- gtkrawgallery-0.9.8/src/gdata/tlslite/utils/Python_RSAKey.py 2011-10-18 19:08:56.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gdata/tlslite/utils/Python_RSAKey.py 2013-09-24 08:50:04.000000000 +0000 @@ -1,5 +1,5 @@ -"""Pure-Python RSA implementation.""" - +"""Pure-Python RSA implementation.""" + from cryptomath import * import xmltools from ASN1Parser import ASN1Parser @@ -8,7 +8,7 @@ class Python_RSAKey(RSAKey): def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0): if (n and not e) or (e and not n): - raise AssertionError() + raise AssertionError() self.n = n self.e = e self.d = d @@ -122,61 +122,61 @@ bytes = base64ToBytes(s) return Python_RSAKey._parsePKCS8(bytes) else: - start = s.find("-----BEGIN RSA PRIVATE KEY-----") - if start != -1: - end = s.find("-----END RSA PRIVATE KEY-----") - if end == -1: - raise SyntaxError("Missing PEM Postfix") - s = s[start+len("-----BEGIN RSA PRIVATE KEY -----") : end] - bytes = base64ToBytes(s) - return Python_RSAKey._parseSSLeay(bytes) - raise SyntaxError("Missing PEM Prefix") - parsePEM = staticmethod(parsePEM) - - def parseXML(s): + start = s.find("-----BEGIN RSA PRIVATE KEY-----") + if start != -1: + end = s.find("-----END RSA PRIVATE KEY-----") + if end == -1: + raise SyntaxError("Missing PEM Postfix") + s = s[start+len("-----BEGIN RSA PRIVATE KEY -----") : end] + bytes = base64ToBytes(s) + return Python_RSAKey._parseSSLeay(bytes) + raise SyntaxError("Missing PEM Prefix") + parsePEM = staticmethod(parsePEM) + + def parseXML(s): element = xmltools.parseAndStripWhitespace(s) - return Python_RSAKey._parseXML(element) + return Python_RSAKey._parseXML(element) parseXML = staticmethod(parseXML) - - def _parsePKCS8(bytes): - p = ASN1Parser(bytes) - - version = p.getChild(0).value[0] - if version != 0: - raise SyntaxError("Unrecognized PKCS8 version") - - rsaOID = p.getChild(1).value - if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: - raise SyntaxError("Unrecognized AlgorithmIdentifier") - - #Get the privateKey - privateKeyP = p.getChild(2) - - #Adjust for OCTET STRING encapsulation - privateKeyP = ASN1Parser(privateKeyP.value) - - return Python_RSAKey._parseASN1PrivateKey(privateKeyP) - _parsePKCS8 = staticmethod(_parsePKCS8) - - def _parseSSLeay(bytes): - privateKeyP = ASN1Parser(bytes) - return Python_RSAKey._parseASN1PrivateKey(privateKeyP) + + def _parsePKCS8(bytes): + p = ASN1Parser(bytes) + + version = p.getChild(0).value[0] + if version != 0: + raise SyntaxError("Unrecognized PKCS8 version") + + rsaOID = p.getChild(1).value + if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: + raise SyntaxError("Unrecognized AlgorithmIdentifier") + + #Get the privateKey + privateKeyP = p.getChild(2) + + #Adjust for OCTET STRING encapsulation + privateKeyP = ASN1Parser(privateKeyP.value) + + return Python_RSAKey._parseASN1PrivateKey(privateKeyP) + _parsePKCS8 = staticmethod(_parsePKCS8) + + def _parseSSLeay(bytes): + privateKeyP = ASN1Parser(bytes) + return Python_RSAKey._parseASN1PrivateKey(privateKeyP) _parseSSLeay = staticmethod(_parseSSLeay) - - def _parseASN1PrivateKey(privateKeyP): - version = privateKeyP.getChild(0).value[0] - if version != 0: - raise SyntaxError("Unrecognized RSAPrivateKey version") - n = bytesToNumber(privateKeyP.getChild(1).value) - e = bytesToNumber(privateKeyP.getChild(2).value) - d = bytesToNumber(privateKeyP.getChild(3).value) - p = bytesToNumber(privateKeyP.getChild(4).value) - q = bytesToNumber(privateKeyP.getChild(5).value) - dP = bytesToNumber(privateKeyP.getChild(6).value) - dQ = bytesToNumber(privateKeyP.getChild(7).value) - qInv = bytesToNumber(privateKeyP.getChild(8).value) - return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) - _parseASN1PrivateKey = staticmethod(_parseASN1PrivateKey) + + def _parseASN1PrivateKey(privateKeyP): + version = privateKeyP.getChild(0).value[0] + if version != 0: + raise SyntaxError("Unrecognized RSAPrivateKey version") + n = bytesToNumber(privateKeyP.getChild(1).value) + e = bytesToNumber(privateKeyP.getChild(2).value) + d = bytesToNumber(privateKeyP.getChild(3).value) + p = bytesToNumber(privateKeyP.getChild(4).value) + q = bytesToNumber(privateKeyP.getChild(5).value) + dP = bytesToNumber(privateKeyP.getChild(6).value) + dQ = bytesToNumber(privateKeyP.getChild(7).value) + qInv = bytesToNumber(privateKeyP.getChild(8).value) + return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) + _parseASN1PrivateKey = staticmethod(_parseASN1PrivateKey) def _parseXML(element): try: diff -Nru gtkrawgallery-0.9.8/src/gtkrawgallery gtkrawgallery-0.9.9/src/gtkrawgallery --- gtkrawgallery-0.9.8/src/gtkrawgallery 2013-03-21 04:10:42.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrawgallery 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -#!/usr/bin/env python -''' -gtkrawgallery: GTKRawGallery launcher for Linux; -To start GTKRawGallery type from a shell prompt: -$ python /usr/bin/gtkrawgallery -or -$ python /usr/local/bin/gtkrawgallery -depending of your system prefix. -comment: A workflow oriented photo manager for digital camera raw image development; -author: (c) 2011 Daniele Isca -license: GNU GPL v.3 (see COPYING file) -version: 0.9.7 -This software comes with no warranty of any kind, use at your own risk! -''' - -import gtkrawgallery - -if __name__ == "__main__": - gtkrg = gtkrawgallery.Gallery() - gtkrg.main() - diff -Nru gtkrawgallery-0.9.8/src/gtkrc gtkrawgallery-0.9.9/src/gtkrc --- gtkrawgallery-0.9.8/src/gtkrc 2012-12-11 05:32:46.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrc 2013-06-17 09:10:52.000000000 +0000 @@ -45,24 +45,24 @@ text[INSENSITIVE] = "#bfbfbf" # Unknown -## engine "murrine" -## { -## menuitemstyle = 2 # 0 = flat, 1 = glassy, 2 = striped -### scrollbar_color = "#78A10C" -## contrast = 4.0 -## glazestyle = 2 # 0 = flat hilight, 1 = curved hilight, 2 = concave style -## menubarstyle = 3 # 0 = flat, 1 = glassy, 2 = gradient, 3 = striped -## menubaritemstyle = 0 # 0 = menuitem look, 1 = button look -## menuitemstyle = 2 # 0 = flat, 1 = glassy, 2 = striped -## listviewheaderstyle = 0 # 0 = flat, 1 = glassy -## roundness = 0 # 0 = squared, 1 = old default, more will increase roundness -## animation = TRUE # FALSE = disabled, TRUE = enabled -## highlight_shade = 1.0 -### gradients = TRUE -## menustyle = 0 -## listviewstyle = 1 -## scrollbarstyle = 3 -## } + engine "murrine" + { + menuitemstyle = 2 # 0 = flat, 1 = glassy, 2 = striped + scrollbar_color = "#78A10C" + contrast = 4.0 + glazestyle = 2 # 0 = flat hilight, 1 = curved hilight, 2 = concave style + menubarstyle = 3 # 0 = flat, 1 = glassy, 2 = gradient, 3 = striped + menubaritemstyle = 0 # 0 = menuitem look, 1 = button look + menuitemstyle = 2 # 0 = flat, 1 = glassy, 2 = striped + listviewheaderstyle = 0 # 0 = flat, 1 = glassy + roundness = 0 # 0 = squared, 1 = old default, more will increase roundness + animation = TRUE # FALSE = disabled, TRUE = enabled + highlight_shade = 1.0 + gradients = TRUE + menustyle = 0 + listviewstyle = 1 + scrollbarstyle = 3 + } } diff -Nru gtkrawgallery-0.9.8/src/gtkrg.ui gtkrawgallery-0.9.9/src/gtkrg.ui --- gtkrawgallery-0.9.8/src/gtkrg.ui 2013-03-04 10:34:50.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg.ui 2013-06-17 08:06:28.000000000 +0000 @@ -2,9 +2,8 @@ - - 1.01 + 1 10 0.01 @@ -802,7 +801,7 @@ - 700 + 750 460 5 Options @@ -887,6 +886,7 @@ True + 5 Add Border @@ -903,17 +903,9 @@ True - model14 - - - - 0 - - False - 10 1 @@ -1420,7 +1412,6 @@ True - 0 Needs restarting! @@ -1620,7 +1611,6 @@ 10 - 180 True False False @@ -1682,7 +1672,6 @@ True - vertical 10 @@ -1719,7 +1708,6 @@ True - vertical 10 @@ -1756,7 +1744,10 @@ True True + Insert here the file extension you know to be supported by dcraw. + True + True 0 @@ -1767,6 +1758,7 @@ True True True + Add to list True @@ -1785,6 +1777,7 @@ True True True + remove from list True @@ -1893,6 +1886,125 @@ button7 + + 330 + 222 + 5 + Open with.. + False + True + center + dialog + window1 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK + 2 + + + True + + + True + 0.17000000178813934 + gtk-execute + 6 + + + 0 + + + + + True + + + True + Command: + + + False + 0 + + + + + True + 11 + + + True + True + True + True + True + True + + + + + 1 + + + + + 1 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK + end + + + gtk-cancel + True + False + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button8 + button9 + + 310 150 @@ -1915,7 +2027,7 @@ True 0.70999997854232788 2 - 0 + 0 False @@ -1938,7 +2050,7 @@ True 0.68000000715255737 2 - 100 + 100 False @@ -2191,6 +2303,95 @@ button85 + + 300 + 150 + 5 + Save curve + False + True + normal + window2 + + + True + 2 + + + True + + + True + Insert a curve name + + + 0 + + + + + True + True + + True + True + + + 1 + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button102 + button120 + + 300 180 @@ -2337,7 +2538,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - label 1 @@ -2553,7 +2753,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - The workflow was saved for next conversion! 1 @@ -3198,7 +3397,7 @@ 366 180 - Add to album + Add to album False center dialog @@ -3250,7 +3449,7 @@ end - New Album + New Album True True True @@ -3463,7 +3662,7 @@ 600 600 5 - Save As + Save As False True center @@ -3896,6 +4095,11 @@ gtk-missing-image 1 + + True + gtk-remove + 1 + True gtk-network @@ -3951,6 +4155,26 @@ gtk-missing-image 1 + + True + gtk-missing-image + 1 + + + True + gtk-clear + 1 + + + True + gtk-edit + 1 + + + True + gtk-missing-image + 1 + True gtk-cdrom @@ -4037,6 +4261,7 @@ + True @@ -4072,6 +4297,33 @@ + + True + + + Remove tag + True + image128 + False + + + + + Clear all tags + True + image141 + False + + + + + Rename tag + True + image142 + False + + + True @@ -4454,23 +4706,6 @@ - - - - - - - - White - - - Grey - - - Black - - - @@ -4688,23 +4923,6 @@ - - - - - - - - Keep embedded profile as input profile - - - Ignore embedded profile - - - Ask always - - - @@ -4982,7 +5200,6 @@ Unlike keyword tagging, metadata is always written! To avoid rebuilding the same tag list, you can save it as a template to quickly reloading later. - 500 400 @@ -6155,7 +6372,6 @@ True - vertical True @@ -7589,13 +7805,6 @@ True - model22 - - - - 0 - - 1 @@ -8575,7 +8784,6 @@ True - vertical True @@ -8599,7 +8807,6 @@ True - vertical 15 @@ -8622,7 +8829,6 @@ True - vertical 8 bit @@ -9978,7 +10184,7 @@ True 0 - Mean: + Mean: False @@ -9989,7 +10195,7 @@ True 0 - Std Dev: + Std Dev: False @@ -10001,7 +10207,7 @@ True 0 - Median: + Median: False @@ -10013,7 +10219,7 @@ True 0 - Pixels: + Pixels: False @@ -10024,7 +10230,7 @@ True 0 - x: + x: False @@ -10963,7 +11169,6 @@ True - vertical True @@ -13666,7 +13871,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 - % + % 2 @@ -13701,7 +13906,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 - % + % 2 @@ -14279,7 +14484,7 @@ True - formula Rsrc = r * (A*r^3 + B*r^2 + C*r + D) + formula Rsrc = r * (A*r^3 + B*r^2 + C*r + D) 0 @@ -14291,7 +14496,7 @@ True - A: + A: 0 @@ -14317,7 +14522,7 @@ True - B: + B: 2 @@ -14343,7 +14548,7 @@ True - C: + C: 4 @@ -14368,7 +14573,7 @@ True - D: + D: 6 @@ -18062,7 +18267,7 @@ True - 0 + 0 @@ -18079,7 +18284,7 @@ True - 255 + 255 @@ -18287,7 +18492,7 @@ - Apply workflow + Apply style True True False @@ -19446,265 +19651,4 @@ - - 300 - 150 - 5 - Save curve - False - True - normal - window2 - False - - - True - vertical - 2 - - - True - vertical - - - True - Insert a curve name - - - 0 - - - - - True - True - - - - 1 - - - - - 1 - - - - - True - end - - - gtk-cancel - True - True - True - True - - - False - False - 0 - - - - - gtk-ok - True - True - True - False - True - - - False - False - 1 - - - - - False - end - 0 - - - - - - button102 - button120 - - - - 330 - 222 - 5 - Open with.. - False - True - center - dialog - window1 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - 2 - - - True - - - True - 0.17000000178813934 - gtk-execute - 6 - - - 0 - - - - - True - - - True - Command: - - - False - 0 - - - - - True - 11 - - - True - True - True - True - True - True - - - - - 1 - - - - - 1 - - - - - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK - end - - - gtk-cancel - True - False - False - True - - - False - False - 0 - - - - - gtk-ok - True - True - True - False - True - - - False - False - 1 - - - - - False - end - 0 - - - - - - button8 - button9 - - - - True - - - Remove tag - True - image128 - False - - - - - Clear all tags - True - image141 - False - - - - - Rename tag - True - image142 - False - - - - - True - gtk-remove - 1 - - - True - gtk-clear - 1 - - - True - gtk-edit - 1 - - - True - gtk-missing-image - 1 - - - True - gtk-missing-image - 1 - diff -Nru gtkrawgallery-0.9.8/src/gtkrg_dropbox.py gtkrawgallery-0.9.9/src/gtkrg_dropbox.py --- gtkrawgallery-0.9.8/src/gtkrg_dropbox.py 2013-03-21 04:03:40.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg_dropbox.py 2013-09-24 08:49:56.000000000 +0000 @@ -1,7 +1,7 @@ -# -*- coding: cp1252 -*- +# -*- coding: utf-8 -*- ''' GTKRawGallery DropBox uploader; -copyright 2013 Daniele Isca +copyright © 2013 Daniele Isca license: GNU GPL v.3 (see COPYING.txt); This software comes with no warranty of any kind, use at your own risk! ''' @@ -12,7 +12,12 @@ import traceback import sys import time -from dbupload import DropboxConnection +import dropbox +import webkit +from gtkrg_profiles import dbx_data + +def _decode(data): + return ''.join([chr(i) for i in data]) class Publisher: @@ -21,6 +26,9 @@ self.image_list = image_list self.stop = False self.uploading = False + self.token_key = None + self.token_secret = None + self.session = None wtree = gtk.Builder() wtree.add_from_file(os.path.join(gtkrg_datadir, 'publisher.ui')) self.initialize_widgets(wtree) @@ -30,30 +38,78 @@ self.combobox1.pack_start(cell, True) self.combobox1.add_attribute(cell, 'text', 0) self.window1.set_icon_from_file(self.gallery.get_icon_path('dropbox.png')) - self.dialog1.set_icon_from_file(self.gallery.get_icon_path('dropbox.png')) self.make_dp_treeview() - self.entry3.set_activates_default(True) - self.dialog1.set_default_response(-5) - self.connect_signals() self.window1.set_title(_('Upload into DropBox')) - self.dialog1.set_title(_('Log into DropBox')) + user = '' if self.gallery.settings.has_key('dropbox_user'): - self.entry2.set_text(self.gallery.settings['dropbox_user']) - if self.gallery.settings.has_key('dropbox_pwd'): - self.entry3.set_text(self.gallery.settings['dropbox_pwd']) + user = self.gallery.settings['dropbox_user'] + if self.gallery.settings.has_key('dropbox_tokens') and self.gallery.settings['dropbox_tokens'].has_key(user): + self.token_key = self.gallery.settings['dropbox_tokens'][user]['key'] + self.token_secret = self.gallery.settings['dropbox_tokens'][user]['secret'] + if not self.gallery.settings.has_key('dropbox_tokens'): + self.gallery.settings['dropbox_tokens'] = {} self.checkbutton1.set_property('visible', False) self.checkbutton2.set_property('visible', False) self.checkbutton3.set_property('visible', False) self.combobox2.set_property('visible', False) self.label5.set_property('visible', False) self.label4.set_property('visible', False) - self.radiobutton2.set_property('visible', False) - self.entry1.set_property('visible', False) + self.label6.set_text(_('Dropbox user:')) + self.radiobutton2.set_label(_('New folder')) + self.radiobutton2.set_tooltip_text(_('creates a new destination folder')) + self.entry1.set_tooltip_text(_('insert the folder name')) self.entry4.set_property('visible', False) - self.radiobutton1.destroy() - label = gtk.Label('Upload to') - self.table1.attach(label,0,1,0,1,0,0,5,0) - label.show() + self.radiobutton1.set_label(_('Upload to:')) + self.boxmodel3 = gtk.ListStore(str) + cell = gtk.CellRendererText() + self.combobox3.set_model(self.boxmodel3) + self.combobox3.pack_start(cell, True) + self.combobox3.add_attribute(cell, 'text', 0) + # loads webview UI + wview = gtk.Builder() + wview.add_from_file(os.path.join(gtkrg_datadir, 'webview.ui')) + self.browser = wview.get_object('window1') + self.scrolled_window = wview.get_object('scrolledwindow1') + self.wview_button = wview.get_object('button1') + self.web_view = webkit.WebView() + self.scrolled_window.add(self.web_view) + self.connect_signals() + self.browser.set_title(_('Log into Dropbox')) + self.browser.set_icon_from_file(self.gallery.get_icon_path('dropbox.png')) + self.browser.set_default_size(800, 600) + self.browser.set_keep_above(True) + if self.gallery.settings.has_key('dropbox_tokens'): + self.combobox3.handler_block(self.id3) + for key in sorted(self.gallery.settings['dropbox_tokens'].keys()): + self.boxmodel3.append([key]) + for row in self.boxmodel3: + if row[0] == user: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) + self.combobox3.handler_unblock(self.id3) + if self.gallery.window3.get_property('visible'): + self.window1.set_transient_for(self.gallery.window3) + elif self.gallery.window2.get_property('visible'): + self.window1.set_transient_for(self.gallery.window2) + else: + self.window1.set_transient_for(self.gallery.window1) + + def load_started(self, *obj): + if self.browser.window: + self.browser.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + + def load_finished(self, *obj): + if self.browser.window: + self.browser.window.set_cursor(None) + + def load_error(self, *obj): + self.web_view.load_string(_('
Page Loading Error!
'), 'text/html', 'UTF-8', '#') + + def hide_webview(self, obj, event=None): + self.browser.hide() + return True def make_dp_treeview(self): self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str) @@ -96,6 +152,17 @@ self.window1.connect('delete_event', self.destroy) self.window1.connect('key_press_event', self.on_key_press_event) self.button5.connect('clicked', self.change_user_account) + self.id3 = self.combobox3.connect('changed', self.change_user_account) + self.radiobutton1.connect('toggled', self.on_radiobutton_toggled) + self.web_view.connect('load_error', self.load_error) + self.web_view.connect('load_finished', self.load_finished) + self.web_view.connect('load_started', self.load_started) + self.browser.connect('delete_event', self.hide_webview) + self.wview_button.connect('clicked', self.get_token_cb) + + def on_radiobutton_toggled(self, obj): + self.entry1.set_sensitive(not obj.get_active()) + self.combobox1.set_sensitive(obj.get_active()) def on_key_press_event(self, obj, event): keyname = gtk.gdk.keyval_name(event.keyval) @@ -107,7 +174,6 @@ def populate_dir_box(self, dirs, text=None): self.boxmodel1.clear() - self.boxmodel1.append(["/"]) for dir_ in sorted(dirs): self.boxmodel1.append([dir_]) if text: @@ -120,13 +186,25 @@ def upload(self, obj=None): if len(self.model): + if self.radiobutton1.get_active(): + remote_dir = self.combobox1.get_active_text() + else: + remote_dir = self.entry1.get_text() or '' + if not remote_dir.startswith('/'): + remote_dir = '/' + remote_dir + remote_dir = os.path.normpath(remote_dir).replace('\\','/') + if self.client: + try: + self.client.file_create_folder(remote_dir) + except dropbox.rest.ErrorResponse, e: + sys.stderr.write(e.error) for row in self.model: self.model.set_value(row.iter, 2, '') - if self.connection.is_logged_in(): + if self.client: self.button2.set_sensitive(False) self.stop = False self.uploading = True - self.upload_to_dropbox() + self.upload_to_dropbox(remote_dir) self.stop = False self.uploading = False self.button2.set_sensitive(True) @@ -134,8 +212,7 @@ self.label3.set_text(_('not logged!')) self.dropbox_login() - def upload_to_dropbox(self): - remote_dir = self.combobox1.get_active_text() + def upload_to_dropbox(self, remote_dir): fraction = 0.99 / len(self.model) self.progressbar1.set_fraction(0) self.progressbar1.show() @@ -149,11 +226,15 @@ self.model.set_value(row.iter, 2, 'uploading...') while gtk.events_pending(): gtk.main_iteration(False) - if not remote_dir.startswith('/'): - remote_dir = '/' + remote_dir try: - self.connection.upload_file(image, remote_dir, image) + f = open(image, 'rb') + resp = self.client.put_file(remote_dir+ '/' + row[1], f) + print resp + f.close() self.model.set_value(row.iter, 2, 'completed!') + except dropbox.rest.ErrorResponse, e: + self.model.set_value(row.iter, 2, 'error!') + sys.stderr.write(e.error) except: self.model.set_value(row.iter, 2, 'error!') sys.stderr.write(traceback.format_exc()) @@ -169,6 +250,7 @@ def refresh_treeview(self): self.rawpath = {} + self.model.clear() for item in self.gallery.selected: basename = os.path.basename(self.gallery.get_real_path(item)) self.rawpath[basename] = item @@ -179,88 +261,112 @@ self.model.append([pixbuf, basename, None]) while gtk.events_pending(): gtk.main_iteration(False) - - def dialog(self): - self.dialog1.show() - if self.entry2.get_text(): - self.entry3.grab_focus() - while gtk.events_pending(): - gtk.main_iteration(False) - res = self.dialog1.run() - if res in [-4, -6]: - self.dialog1.hide() - return None + + def change_user_account(self, obj, event=None): + if obj == self.button5: + self.token_key = None + self.token_secret = None else: - user = self.entry2.get_text() - if not user: - self.dialog() - pwd = self.entry3.get_text() - if not pwd: - self.dialog() - self.dialog1.hide() + user = obj.get_active_text() + if user: + self.label3.set_text('') + while gtk.events_pending(): + gtk.main_iteration(False) + if self.gallery.settings['dropbox_tokens'].has_key(user): + self.token_key = self.gallery.settings['dropbox_tokens'][user].get('key') + self.token_secret = self.gallery.settings['dropbox_tokens'][user].get('secret') + else: + self.token_key = None + self.token_secret = None + self.dropbox_login() + + def dropbox_login(self): + if self.token_key and self.token_secret: + self.get_token_cb(None, self.token_key, self.token_secret) + else: + if self.window1.get_property('visible'): + self.window1.iconify() + self.wview_button.set_label(_('Continue')) + self.browser.show_all() + self.web_view.load_string(_('
Log into Dropbox to authorize GTKRawGallery and click OK.

Click Continue.

'), 'text/html', 'UTF-8', '#') while gtk.events_pending(): gtk.main_iteration(False) - return user, pwd - def change_user_account(self, obj): - self.label3.set_text('') - while gtk.events_pending(): - gtk.main_iteration(False) - self.refresh_db_dirs() - - def refresh_db_dirs(self): - self.window1.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - while gtk.events_pending(): - gtk.main_iteration(False) - if self.dropbox_login(): + def get_dropbox_session(self): try: - if self.connection.is_logged_in(): - self.label3.set_text(_('Retrieving directories...')) - while gtk.events_pending(): - gtk.main_iteration(False) - dirs = self.connection.get_dir_list('/') - self.populate_dir_box(dirs) - self.button2.set_sensitive(True) - self.label3.set_text(_('You are logged!')) - else: - self.label3.set_text(_('Login failed!')) - sys.stderr.write(traceback.format_exc()) - self.window1.window.set_cursor(None) - return True + self.session = dropbox.session.DropboxSession(_decode(dbx_data[0]), _decode(dbx_data[1]), "dropbox") + self.request_token = self.session.obtain_request_token() + url = self.session.build_authorize_url(self.request_token) + self.web_view.load_uri(url) + except dropbox.rest.ErrorResponse, e: + gtk.gdk.beep() + self.label3.set_text(_('Log-in failed!')) + sys.stderr.write(e.error) except: - self.label3.set_text(_('Login failed!')) - self.window1.window.set_cursor(None) - return True - self.window1.window.set_cursor(None) - return False - - def dropbox_login(self): - credentials = self.dialog() - if credentials: - username, pwd = credentials - else: - return None - try: - self.label3.set_text(_('authenticating...')) + gtk.gdk.beep() + self.label3.set_text(_('Log-in failed!')) + sys.stderr.write(traceback.format_exc()) + + def get_token_cb(self, obj=None, t_key=None, t_secret=None): + if obj == self.wview_button and obj.get_label() != 'gtk-ok': + obj.set_label('gtk-ok') while gtk.events_pending(): gtk.main_iteration(False) - self.connection = DropboxConnection(username, pwd) - self.gallery.settings['dropbox_user'] = username - self.gallery.settings['dropbox_pwd'] = pwd - self.window1.set_title(_('Upload into %s DropBox' % username)) - self.label3.set_text(_('You are logged!')) - except: + self.get_dropbox_session() + return True + if self.browser.get_property('visible'): + self.browser.hide() + self.window1.present() + self.label3.set_text(_('authenticating...')) + self.window1.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + while gtk.events_pending(): + gtk.main_iteration(False) + try: + if not self.session: + self.session = dropbox.session.DropboxSession(_decode(dbx_data[0]), _decode(dbx_data[1]), "dropbox") + if t_key: + self.session.set_token(t_key, t_secret) + access_token = self.session.token + else: + access_token = self.session.obtain_access_token(self.request_token) + self.client = dropbox.client.DropboxClient(self.session) + info = self.client.account_info() + if info: + self.label3.set_text(_('Retrieving directories...')) + while gtk.events_pending(): + gtk.main_iteration(False) + metadata = self.client.metadata("/") + dirs = [] + for item in metadata['contents']: + if item['is_dir']: + dirs.append(item['path']) + self.populate_dir_box(dirs) + self.button2.set_sensitive(True) + self.window1.set_title(_('Upload into the DropBox of %s') % info['display_name']) + self.label3.set_text(_('You are logged!')) + self.gallery.settings['dropbox_user'] = info['display_name'] + self.gallery.settings['dropbox_tokens'][info['display_name']] = {'key':access_token.key, 'secret':access_token.secret} + if self.gallery.settings.has_key('dropbox_tokens'): + self.combobox3.handler_block(self.id3) + for key in sorted(self.gallery.settings['dropbox_tokens'].keys()): + self.boxmodel3.append([key]) + for row in self.boxmodel3: + if row[0] == info['display_name']: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) + self.combobox3.handler_unblock(self.id3) + except dropbox.rest.ErrorResponse, e: gtk.gdk.beep() self.label3.set_text(_('Log-in failed!')) sys.stderr.write(traceback.format_exc()) - return None - return True - + self.window1.window.set_cursor(None) + def main(self): - self.window1.show() self.progressbar1.hide() self.refresh_treeview() - self.refresh_db_dirs() + self.dropbox_login() def destroy(self, *obj): if self.uploading: @@ -271,6 +377,8 @@ while gtk.events_pending(): gtk.main_iteration(False) self.model.clear() + self.window1.destroy() + self.browser.destroy() return True diff -Nru gtkrawgallery-0.9.8/src/gtkrg_facebook.py gtkrawgallery-0.9.9/src/gtkrg_facebook.py --- gtkrawgallery-0.9.8/src/gtkrg_facebook.py 2013-03-21 04:03:40.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg_facebook.py 2013-09-24 08:49:56.000000000 +0000 @@ -1,7 +1,7 @@ -# -*- coding: cp1252 -*- +# -*- coding: utf-8 -*- ''' GTKRawGallery Facebook publisher; -copyright 2013 Daniele Isca +copyright © 2013 Daniele Isca license: GNU GPL v.3 (see COPYING.txt); This software comes with no warranty of any kind, use at your own risk! ''' @@ -10,18 +10,20 @@ import os.path import os import traceback -import time import sys +import webkit +import urlparse +import time from gtkrg_profiles import fb_data1 from subprocess import * class Publisher: def __init__(self, gallery, image_list, gtkrg_datadir): + self.state = None self.gallery = gallery self.image_list = image_list self.formats = ('.png', '.jpg', '.jpeg') - self.browser = None self.token = None self.stop = False self.uploading = False @@ -30,29 +32,24 @@ self.redirect_url = 'https://www.facebook.com/connect/login_success.html' self.logout_url = 'https://www.facebook.com/logout.php?next=%s&access_token=%s' self.scope = 'user_photos,publish_stream,offline_access' - self.login_url = 'https://graph.facebook.com/oauth/authorize?client_id=%s&redirect_uri=%s&scope=%s&response_type=token' % (self.decode_(fb_data1), self.redirect_url, self.scope) + self.login_url = 'https://graph.facebook.com/oauth/authorize?client_id=%s&redirect_uri=%s&scope=%s&response_type=token' % (self._decode(fb_data1), self.redirect_url, self.scope) wtree = gtk.Builder() wtree.add_from_file(os.path.join(gtkrg_datadir, 'publisher.ui')) self.initialize_widgets(wtree) - self.boxmodel1 = gtk.ListStore(str) cell = gtk.CellRendererText() self.combobox1.set_model(self.boxmodel1) self.combobox1.pack_start(cell, True) self.combobox1.add_attribute(cell, 'text', 0) - + self.boxmodel3 = gtk.ListStore(str) + cell = gtk.CellRendererText() + self.combobox3.set_model(self.boxmodel3) + self.combobox3.pack_start(cell, True) + self.combobox3.add_attribute(cell, 'text', 0) self.window1.set_icon_from_file(self.gallery.get_icon_path('facebook.png')) - self.dialog1.set_icon_from_file(self.gallery.get_icon_path('facebook.png')) self.make_fb_treeview() - self.entry3.set_activates_default(True) - self.dialog1.set_default_response(-5) - self.connect_signals() - if self.gallery.settings.has_key('fb_user'): - self.entry2.set_text(self.gallery.settings['fb_user']) - if hasattr(self.gallery, 'fb_password'): - self.entry3.set_text(self.gallery.fb_password) if self.gallery.settings.has_key('token') and self.gallery.settings.has_key('fb_user') and self.gallery.settings['token'].get(self.gallery.settings['fb_user']): - self.token = self.decode_(self.gallery.settings['token'][self.gallery.settings['fb_user']]) + self.token = self._decode(self.gallery.settings['token'][self.gallery.settings['fb_user']]) else: self.gallery.settings['token'] = {} self.checkbutton1.set_property('visible', False) @@ -67,7 +64,72 @@ self.boxmodel2.append(['Everyone']) self.boxmodel2.append(['Self']) self.combobox2.set_active(0) + # loads webview UI + wview = gtk.Builder() + wview.add_from_file(os.path.join(gtkrg_datadir, 'webview.ui')) + self.browser = wview.get_object('window1') + self.scrolled_window = wview.get_object('scrolledwindow1') + self.wview_button = wview.get_object('button1') + self.web_view = webkit.WebView() + self.scrolled_window.add(self.web_view) + self.browser.set_title(_('Log into Facebook')) + self.browser.set_icon_from_file(self.gallery.get_icon_path('facebook.png')) + self.browser.set_keep_above(True) + self.connect_signals() + if self.gallery.settings.has_key('token'): + for key in sorted(self.gallery.settings['token'].keys()): + self.boxmodel3.append([key]) + self.fb_user = self.gallery.settings.get('fb_user') + self.combobox3.handler_block(self.id3) + for row in self.boxmodel3: + if row[0] == self.fb_user: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) + self.combobox3.handler_unblock(self.id3) + if self.gallery.window3.get_property('visible'): + self.window1.set_transient_for(self.gallery.window3) + elif self.gallery.window2.get_property('visible'): + self.window1.set_transient_for(self.gallery.window2) + else: + self.window1.set_transient_for(self.gallery.window1) + + def load_error(self, *obj): + self.web_view.load_string(_('
Page Loading Error!
'), 'text/html', 'UTF-8', '#') + def load_started(self, *obj): + if self.browser.window: + self.browser.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + + def load_finished(self, *obj): + if self.browser.window: + self.browser.window.set_cursor(None) + + def hide_webview(self, obj, event=None): + if obj == self.wview_button and self.wview_button.get_label() != 'gtk-ok': + obj.set_label('gtk-ok') + self.fb_login() + else: + self.browser.hide() + return True + + def _load_committed_cb(self, web_view, frame): + uri = frame.get_uri() + parse = urlparse.urlparse(uri) + if (hasattr(parse, 'netloc') and hasattr(parse, 'path') and hasattr(parse, 'fragment') and parse.netloc == 'www.facebook.com' and parse.path == '/connect/login_success.html'): + if parse.fragment: + params = urlparse.parse_qs(parse.fragment) + self.token = params['access_token'][0] + # token_expire = params['expires_in'][0] + if self.token: + self.browser.hide() + self.refresh_facebook_albums() + else: + if self.state == 'logout': + self.token = False + self.fb_login() + def on_renderer_toggled(self, cell, path, model, position): model[path][position] = not model[path][position] @@ -123,7 +185,14 @@ self.window1.connect('delete_event', self.destroy) self.window1.connect('key_press_event', self.on_key_press_event) self.radiobutton1.connect('toggled', self.on_radiobutton_toggled) - self.button5.connect('clicked', self.change_user_account) + self.button5.connect('clicked', self.new_login) + self.id3 = self.combobox3.connect('changed', self.change_user_account) + self.web_view.connect('load_committed', self._load_committed_cb) # Load page + self.web_view.connect('load_error', self.load_error) + self.web_view.connect('load_finished', self.load_finished) + self.web_view.connect('load_started', self.load_started) + self.wview_button.connect('clicked', self.hide_webview) + self.browser.connect('delete_event', self.hide_webview) def on_radiobutton_toggled(self, obj): self.label4.set_sensitive(not obj.get_active()) @@ -305,6 +374,7 @@ def refresh_treeview(self): self.rawpath = {} + self.model.clear() for item in self.gallery.selected: basename = os.path.basename(self.gallery.get_real_path(item)) self.rawpath[basename] = item @@ -316,16 +386,27 @@ while gtk.events_pending(): gtk.main_iteration(False) - def change_user_account(self, obj): - self.token = None - if not self.refresh_facebook_albums(): - self.destroy(None) + def new_login(self, obj): + self.window1.iconify() + while gtk.events_pending(): + gtk.main_iteration(False) + self.wview_button.set_label(_('Continue')) + if self.token: + self.state = 'logout' + self.stop = True + self.fb_logout() + else: + self.fb_login() + + def change_user_account(self, obj, event=None): + username = obj.get_active_text() + if username: + self.token = self._decode(self.gallery.settings['token'][username]) + self.refresh_facebook_albums(username) - def refresh_facebook_albums(self, login=True): + def refresh_facebook_albums(self, username=None): + self.window1.present() self.window1.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - while not self.token: - if not self.fb_login(login): - break if self.token: self.label3.set_text(_('Retrieving albums...')) while gtk.events_pending(): @@ -341,16 +422,29 @@ self.label3.set_text(_('Token expired!')) while gtk.events_pending(): gtk.main_iteration(False) - if self.gallery.settings['token'].has_key(self.entry2.get_text()): - del self.gallery.settings['token'][self.entry2.get_text()] - self.refresh_facebook_albums() + if username and self.gallery.settings['token'].has_key(username): + del self.gallery.settings['token'][username] + for row in self.boxmodel3: + if row[0] == username: + self.boxmodel3.remove(row.iter) if self.profile: self.window1.set_title(_('Publish on %s Facebook account') % self.profile['name']) while gtk.events_pending(): gtk.main_iteration(False) albums = self.graph.get_connections('me', 'albums') - self.gallery.settings['fb_user'] = self.entry2.get_text() - self.gallery.settings['token'][self.gallery.settings['fb_user']] = self.encode_(self.token) + self.gallery.settings['fb_user'] = self.profile['username'] + self.gallery.settings['token'][self.gallery.settings['fb_user']] = self._encode(self.token) + self.combobox3.handler_block(self.id3) + self.boxmodel3.clear() + for key in self.gallery.settings['token'].keys(): + self.boxmodel3.append([key]) + for row in self.boxmodel3: + if row[0] == self.profile['username']: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) + self.combobox3.handler_unblock(self.id3) album_list = [] if albums: for item in albums['data']: @@ -368,115 +462,35 @@ return False def fb_logout(self): - if not self.browser: - from mechanize import Browser - self.browser = Browser() - self.browser.addheaders = [("User-agent", "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1")] - self.browser.set_handle_robots(False) - response = self.browser.open(self.logout_url % (self.redirect_url, self.token)) + self.web_view.load_uri(self.logout_url % (self.redirect_url, self.token)) def fb_login(self, login=None): - if login: - credentials = self.dialog() - else: - credentials = self.entry2.get_text(), self.entry3.get_text() - if credentials: - if self.gallery.settings['token'].has_key(credentials[0]): - self.token = self.decode_(self.gallery.settings['token'][credentials[0]]) - print self.token - return True - self.label3.set_text(_('Authenticating...')) + if not self.browser.get_property('visible'): + self.browser.show_all() + if self.wview_button.get_label() != 'gtk-ok': + self.web_view.load_string(_('
Log into your Facebook to authorize GTKRawGallery and click OK.

Click Continue.

'), 'text/html', 'UTF-8', '#') while gtk.events_pending(): gtk.main_iteration(False) - from mechanize import Browser - self.browser = Browser() - self.browser.addheaders = [("User-agent", "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1")] - self.browser.set_handle_robots(False) - response = self.browser.open(self.login_url) - url = response.geturl() - if not url.startswith(self.redirect_url + '#access_token='): - try: - n = 0 - for i in self.browser.forms(): - if i.attrs.get('id') == 'login_form': # form id could change! - break - n += 1 - self.browser.select_form(nr=n) # user login - self.browser['email'] = credentials[0] - self.browser['pass'] = credentials[1] - response = self.browser.submit() - url = response.geturl() - print 'LOGIN' - self.token = self.extract_token(url) - if not self.token: # going to app authorization - n = 0 - for i in self.browser.forms(): - if i.attrs.get('id') == 'uiserver_form': # form id could change! - break - n += 1 - self.browser.select_form(nr=n) #app authorization - resp = self.browser.submit() - url = resp.geturl() - self.token = self.extract_token(url) - if self.token: - print 'APP AUTHORIZED' - else: - self.label3.set_text(_('Log-in failed!')) - except: - self.label3.set_text(_('Log-in failed!')) - sys.stderr.write(traceback.format_exc()) - else: - self.token = self.extract_token(url) - self.gallery.fb_password = credentials[1] - return True - else: - return None - - def extract_token(self, url): - import urlparse - result = urlparse.parse_qs(url) - if result.has_key(self.redirect_url +'#access_token'): - return result[self.redirect_url +'#access_token'][0] - print result, 'no access token in url' - return None - - def dialog(self): - self.dialog1.show() - if self.entry2.get_text(): - self.entry3.grab_focus() - while gtk.events_pending(): - gtk.main_iteration(False) - res = self.dialog1.run() - if res in [-4, -6]: - self.dialog1.hide() - return None else: - user = self.entry2.get_text() - if not user: - self.dialog() - pwd = self.entry3.get_text() - if not pwd: - self.dialog() - self.dialog1.hide() - while gtk.events_pending(): - gtk.main_iteration(False) - return user, pwd + self.state = 'login' + self.web_view.load_uri(self.login_url) - def decode_(self, data): + def _decode(self, data): return ''.join([chr(i) for i in data]) - def encode_(self, data): + def _encode(self, data): return [ord(i) for i in data] def main(self): self.gallery.window2.set_modal(False) self.window1.set_title(_('Publish on Facebook')) - self.dialog1.set_title(_('Log into Facebook')) - self.window1.show() self.progressbar1.hide() self.refresh_treeview() - if not self.refresh_facebook_albums(): - self.destroy(None) + if not self.token: + self.wview_button.set_label(_('Continue')) + self.fb_login() + else: + self.refresh_facebook_albums(self.fb_user) def destroy(self, *obj): self.gallery.window2.set_modal(True) @@ -486,12 +500,8 @@ self.gallery.unselect() while gtk.events_pending(): gtk.main_iteration(False) - if self.token: - self.fb_logout() - if self.browser: - self.browser.close() - del self.browser self.model.clear() - return True - + self.browser.destroy() + self.window1.destroy() + diff -Nru gtkrawgallery-0.9.8/src/gtkrg_flickr.py gtkrawgallery-0.9.9/src/gtkrg_flickr.py --- gtkrawgallery-0.9.8/src/gtkrg_flickr.py 2013-03-21 04:03:40.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg_flickr.py 2013-09-24 08:49:56.000000000 +0000 @@ -1,7 +1,7 @@ -# -*- coding: cp1252 -*- +# -*- coding: utf-8 -*- ''' GTKRawGallery Flickr publisher; -copyright 2013 Daniele Isca +copyright © 2013 Daniele Isca license: GNU GPL v.3 (see COPYING.txt); This software comes with no warranty of any kind, use at your own risk! ''' @@ -11,6 +11,9 @@ import os import traceback from subprocess import * +import webkit +import urlparse +import sys class Publisher: @@ -18,9 +21,11 @@ self.gallery = gallery self.tempfile = os.path.join(self.gallery.temp, 'tempfile.tiff') self.image_list = image_list - self.browser = None self.flickr = None + self.token = None + self.frob = None self.stop = False + self.auth_url = None self.uploading = False self.album = {} self.formats = ('.jpg', '.jpeg', '.gif', '.png', '.tiff', '.tif') @@ -32,22 +37,66 @@ self.combobox1.set_model(self.boxmodel1) self.combobox1.pack_start(cell, True) self.combobox1.add_attribute(cell, 'text', 0) + self.boxmodel3 = gtk.ListStore(str) + cell = gtk.CellRendererText() + self.combobox3.set_model(self.boxmodel3) + self.combobox3.pack_start(cell, True) + self.combobox3.add_attribute(cell, 'text', 0) self.window1.set_icon_from_file(self.gallery.get_icon_path('flickr.png')) - self.dialog1.set_icon_from_file(self.gallery.get_icon_path('flickr.png')) self.make_flickr_treeview() - self.entry3.set_activates_default(True) - self.dialog1.set_default_response(-5) - self.connect_signals() self.window1.set_title(_('Publish on Flickr')) - self.dialog1.set_title(_('Log into Flickr')) - if self.gallery.settings.has_key('flickr_user'): - self.entry2.set_text(self.gallery.settings['flickr_user']) - if hasattr(self.gallery, 'flickr_password'): - self.entry3.set_text(self.gallery.flickr_password) + if self.gallery.window3.get_property('visible'): + self.window1.set_transient_for(self.gallery.window3) + elif self.gallery.window2.get_property('visible'): + self.window1.set_transient_for(self.gallery.window2) + else: + self.window1.set_transient_for(self.gallery.window1) self.checkbutton1.set_property('visible', False) self.checkbutton2.set_property('visible', False) self.combobox2.set_property('visible', False) self.label5.set_property('visible', False) + self.label6.set_text(_('Flickr user:')) + # loads webview UI + wview = gtk.Builder() + wview.add_from_file(os.path.join(gtkrg_datadir, 'webview.ui')) + self.browser = wview.get_object('window1') + self.scrolled_window = wview.get_object('scrolledwindow1') + self.wview_button = wview.get_object('button1') + self.web_view = webkit.WebView() + self.scrolled_window.add(self.web_view) + self.connect_signals() + self.browser.set_title(_('Log into Flickr')) + self.browser.set_icon_from_file(self.gallery.get_icon_path('flickr.png')) + self.browser.set_default_size(800, 600) + self.browser.set_keep_above(True) + if not self.gallery.settings.has_key('flickr_tokens'): + self.gallery.settings['flickr_tokens'] = {} + for key in sorted(self.gallery.settings['flickr_tokens'].keys()): + self.boxmodel3.append([key]) + self.combobox3.handler_block(self.id3) + user = self.gallery.settings.get('flickr_user') + for row in self.boxmodel3: + if row[0] == user: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) + self.combobox3.handler_unblock(self.id3) + + def load_started(self, *obj): + if self.browser.window: + self.browser.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + + def load_finished(self, *obj): + if self.browser.window: + self.browser.window.set_cursor(None) + + def load_error(self, *obj): + self.web_view.load_string(_('
Page Loading Error!
'), 'text/html', 'UTF-8', '#') + + def hide_webview(self, obj, event=None): + self.browser.hide() + return True def make_flickr_treeview(self): self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, 'gboolean', 'gboolean', 'gboolean', 'gboolean', str) @@ -147,6 +196,12 @@ self.window1.connect('key_press_event', self.on_key_press_event) self.radiobutton1.connect('toggled', self.on_radiobutton_toggled) self.button5.connect('clicked', self.change_user_account) + self.id3 = self.combobox3.connect('changed', self.change_user_account) + self.web_view.connect('load_error', self.load_error) + self.web_view.connect('load_finished', self.load_finished) + self.web_view.connect('load_started', self.load_started) + self.browser.connect('delete_event', self.hide_webview) + self.wview_button.connect('clicked', self.get_token_cb) def on_radiobutton_toggled(self, obj): self.label4.set_sensitive(not obj.get_active()) @@ -362,11 +417,12 @@ return None def add_to_album(self, album_id, photo_id): - resp = self.flickr.photosets_addPhoto(photoset_id=album_id, photo_id=photo_id, format = 'json') + resp = self.flickr.photosets_addPhoto(photoset_id=album_id, photo_id=photo_id, format='json') print resp def refresh_treeview(self): self.rawpath = {} + self.model.clear() for item in self.gallery.selected: basename = os.path.basename(self.gallery.get_real_path(item)) self.rawpath[basename] = item @@ -377,166 +433,152 @@ self.model.append([pixbuf, basename, '','','', True, False, False, False, None]) while gtk.events_pending(): gtk.main_iteration(False) - - def dialog(self): - self.dialog1.show() - if self.entry2.get_text(): - self.entry3.grab_focus() - while gtk.events_pending(): - gtk.main_iteration(False) - res = self.dialog1.run() - if res in [-4, -6]: - self.dialog1.hide() - return None - else: - user = self.entry2.get_text() - if not user: - self.dialog() - pwd = self.entry3.get_text() - if not pwd: - self.dialog() - self.dialog1.hide() - while gtk.events_pending(): - gtk.main_iteration(False) - return user, pwd def change_user_account(self, obj): - self.label3.set_text('') - while gtk.events_pending(): - gtk.main_iteration(False) - credentials = self.dialog() - username = None - if credentials: - username = credentials[0] or None - self.refresh_flickr_albums(username) + token = None + if obj == self.button5: + self.label3.set_text('') + if self.window1.get_property('visible'): + self.window1.iconify() + while gtk.events_pending(): + gtk.main_iteration(False) + else: + user = self.combobox3.get_active_text() + if user: + token = self.gallery.settings['flickr_tokens'].get(user) + if self.flickr_login(token): + self.window1.present() + self.progressbar1.hide() + self.refresh_flickr_albums() def refresh_flickr_albums(self, username=None): - self.window1.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - if self.flickr_login(username): - try: - resp= self.flickr.test_login() - if resp.attrib['stat'] == 'ok': - self.label3.set_text(_('Retrieving albums...')) - while gtk.events_pending(): - gtk.main_iteration(False) - sets = self.flickr.photosets_getList(user_id=None) - if sets.attrib['stat'] == 'ok': - photosets = sets.find('photosets').findall('photoset') - album_list = [] - if photosets: - for item in photosets: - title = item.find('title').text - self.album[title] = item.attrib['id'] - album_list.append(title) - self.populate_album_box(album_list) - else: - self.label3.set_text('') - self.label3.set_text(_('you have %s albums') % len(album_list)) - self.button2.set_sensitive(True) - else: - self.label3.set_text(_('Login failed!')) - self.window1.window.set_cursor(None) - return True - except: + if self.window1.window: + self.window1.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + try: + resp= self.flickr.test_login() + if resp.attrib['stat'] == 'ok': + self.label3.set_text(_('Retrieving albums...')) + while gtk.events_pending(): + gtk.main_iteration(False) + sets = self.flickr.photosets_getList(user_id=None) + if sets.attrib['stat'] == 'ok': + photosets = sets.find('photosets').findall('photoset') + album_list = [] + if photosets: + for item in photosets: + title = item.find('title').text + self.album[title] = item.attrib['id'] + album_list.append(title) + self.populate_album_box(album_list) + else: + self.label3.set_text('') + self.label3.set_text(_('you have %s albums') % len(album_list)) + self.button2.set_sensitive(True) + else: self.label3.set_text(_('Login failed!')) + self.window1.window.set_cursor(None) + return True + except: + self.label3.set_text(_('Login failed!')) + if self.window1.window: self.window1.window.set_cursor(None) - return True - self.window1.window.set_cursor(None) - return False + return True def flickr_auth(self, frob, perms): - credentials = self.entry2.get_text(), self.entry3.get_text() - if credentials: - self.label3.set_text(_('Authenticating...')) + self.auth_url = self.flickr.auth_url(perms, frob) + self.wview_button.set_label(_('Continue')) + self.web_view.load_string(_('
Log into Flickr to authorize GTKRawGallery and click OK.

Click on continue.

'), 'text/html', 'UTF-8', '#') + self.browser.show_all() while gtk.events_pending(): - gtk.main_iteration(False) - import mechanize - import cookielib - self.browser = mechanize.Browser() - self.browser.addheaders = [("User-agent", "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1")] - self.browser.set_handle_equiv(True) - self.browser.set_handle_redirect(True) - self.browser.set_handle_referer(True) - self.browser.set_handle_robots(False) - cj = cookielib.LWPCookieJar() - self.browser.set_cookiejar(cj) - self.browser.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1) - auth_url = self.flickr.auth_url(perms, frob) - resp = self.browser.open(auth_url) - n = 0 - for i in self.browser.forms(): - if i.attrs.get('id') == 'login_form': - break - n += 1 - self.browser.select_form(nr=n) # user login - self.browser['login'] = credentials[0] - self.browser['passwd'] = credentials[1] - response = self.browser.submit() - print 'LOGIN OK' - try: - n = 0 - for i in self.browser.forms(): - if i.attrs.get('action') == '/services/auth/': - break - n +=1 - self.browser.select_form(nr=n) - res = self.browser.submit() #app authorization - print 'APP AUTHORIZED' - except: - pass - self.gallery.flickr_password = credentials[1] - - def flickr_login(self, username=None): - if not username: - credentials = self.dialog() - if credentials: - username = credentials[0] or None - else: - return None + gtk.main_iteration(False) + + def flickr_login(self, token=None): + self.gallery.window1.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + while gtk.events_pending(): + gtk.main_iteration(False) import flickrapi from gtkrg_profiles import fli_data def decode_(data): return ''.join([chr(i) for i in data]) - self.flickr = flickrapi.FlickrAPI(api_key=decode_(fli_data[0]), secret=decode_(fli_data[1]), username=username) + self.flickr = flickrapi.FlickrAPI(api_key=decode_(fli_data[0]), secret=decode_(fli_data[1]), token=token, store_token=False) try: - (token, frob) = self.flickr.get_token_part_one(perms='write', auth_callback=self.flickr_auth) - if token: + self.token = None + (self.token, self.frob) = self.flickr.get_token_part_one(perms='write', auth_callback=self.flickr_auth) + if self.token: resp= self.flickr.test_login() if resp.attrib['stat'] == 'ok': - self.window1.set_title(_('Publish on %s Flickr account') % resp[0].find('username').text) + username = resp[0].find('username').text + self.window1.set_title(_('Publish on %s Flickr account') % username) self.gallery.settings['flickr_user'] = username + self.gallery.settings['flickr_tokens'][username] = self.token + self.gallery.window1.window.set_cursor(None) + return True else: gtk.gdk.beep() self.label3.set_text(_('Log-in failed!')) + self.gallery.window1.window.set_cursor(None) return None else: - self.flickr.get_token_part_two((token, frob)) - resp = self.flickr.test_login() - if resp.attrib['stat'] == 'ok': - self.window1.set_title(_('Publish on %s Flickr account') % resp[0].find('username').text) - self.gallery.settings['flickr_user'] = username - else: - gtk.gdk.beep() - self.label3.set_text(_('Log-in failed!')) + self.gallery.window1.window.set_cursor(None) return None except: gtk.gdk.beep() self.label3.set_text(_('Log-in failed!')) + self.gallery.window1.window.set_cursor(None) return None - return True - + + def get_token_cb(self, obj): + if self.wview_button.get_label() == 'gtk-ok': + self.browser.hide() + self.progressbar1.hide() + self.window1.present() + self.gallery.window1.window.set_cursor(None) + while gtk.events_pending(): + gtk.main_iteration(False) + token = self.flickr.get_token_part_two((self.token, self.frob)) + resp = self.flickr.test_login() + if resp.attrib['stat'] == 'ok': + username = resp[0].find('username').text + self.window1.set_title(_('Publish on %s Flickr account') % username) + self.gallery.settings['flickr_user'] = username + self.gallery.settings['flickr_tokens'][username] = token + self.combobox3.handler_block(self.id3) + self.boxmodel3.clear() + for key in sorted(self.gallery.settings['flickr_tokens'].keys()): + self.boxmodel3.append([key]) + for row in self.boxmodel3: + if row[0] == username: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) + self.combobox3.handler_unblock(self.id3) + while gtk.events_pending(): + gtk.main_iteration(False) + self.refresh_treeview() + self.refresh_flickr_albums() + else: + gtk.gdk.beep() + self.label3.set_text(_('Log-in failed!')) + self.progressbar1.hide() + else: + if self.auth_url: + self.web_view.load_uri(self.auth_url) + self.wview_button.set_label('gtk-ok') + def main(self): self.gallery.window2.set_modal(False) - self.window1.show() - self.progressbar1.hide() - self.refresh_treeview() - user = None - if self.gallery.settings.has_key('flickr_user'): - user = self.gallery.settings['flickr_user'] - if not self.refresh_flickr_albums(user): - self.refresh_flickr_albums(None) - - # self.destroy() + token = None + user = self.gallery.settings.get('flickr_user') + if user and self.gallery.settings.get('flickr_tokens'): + token = self.gallery.settings['flickr_tokens'].get(user) + if self.flickr_login(token): + self.window1.show() + self.progressbar1.hide() + while gtk.events_pending(): + gtk.main_iteration(False) + self.refresh_treeview() + self.refresh_flickr_albums() def destroy(self, *obj): self.gallery.window2.set_modal(True) @@ -549,10 +591,8 @@ gtk.main_iteration(False) if self.flickr: del self.flickr - if self.browser: - self.browser.close() - del self.browser self.model.clear() - return True + self.window1.destroy() + self.browser.destroy() diff -Nru gtkrawgallery-0.9.8/src/gtkrg_loader.py gtkrawgallery-0.9.9/src/gtkrg_loader.py --- gtkrawgallery-0.9.8/src/gtkrg_loader.py 2013-03-21 04:12:23.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg_loader.py 2013-09-24 08:49:56.000000000 +0000 @@ -1,7 +1,7 @@ -# -*- coding: cp1252 -*- +# -*- coding: utf-8 -*- ''' description: GTKRawGallery library loader; -copyright 2013 Daniele Isca +copyright © 2013 Daniele Isca license: GNU GPL v.3 (see COPYING.txt); This software comes with no warranty of any kind, use at your own risk! ''' @@ -107,12 +107,16 @@ import gtk if gtk.gtk_version < (2, 12, 0): found = False + if gtk.gtk_version >= (3, 0, 0): + found = 3 except: traceback.print_exc() found = False finally: if not found: messagebox_error("GTKRawGallery requires GTK+ 2.12.0 or newer!") + elif found == 3: + messagebox_error("GTK+3 is not yet supported.\nInstall a recent GTK+2 version, please!") return gtk elif name == 'cairo': try: diff -Nru gtkrawgallery-0.9.8/src/gtkrg_picasaweb.py gtkrawgallery-0.9.9/src/gtkrg_picasaweb.py --- gtkrawgallery-0.9.8/src/gtkrg_picasaweb.py 2013-03-21 04:03:40.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg_picasaweb.py 2013-09-24 08:49:56.000000000 +0000 @@ -9,10 +9,12 @@ import pygtk import os.path import os +import sys import traceback import time import mimetypes import gdata.photos +from gdata import service from subprocess import * @@ -33,20 +35,49 @@ self.combobox1.set_model(self.boxmodel1) self.combobox1.pack_start(cell, True) self.combobox1.add_attribute(cell, 'text', 0) + self.boxmodel3 = gtk.ListStore(str) + cell = gtk.CellRendererText() + self.combobox3.set_model(self.boxmodel3) + self.combobox3.pack_start(cell, True) + self.combobox3.add_attribute(cell, 'text', 0) self.window1.set_icon_from_file(self.gallery.get_icon_path('picasa.png')) self.dialog1.set_icon_from_file(self.gallery.get_icon_path('picasa.png')) self.make_pwa_treeview() self.entry3.set_activates_default(True) self.dialog1.set_default_response(-5) - self.window1.set_title('Publish on Picasa Web Albums') - self.dialog1.set_title('Log into Picasa Web Albums') + self.window1.set_title(_('Publish on Picasa Web Albums')) + self.dialog1.set_title(_('Log into Picasa Web Albums')) + self.user = '' + self.pwd = '' if self.gallery.settings.has_key('pwa_user'): - self.entry2.set_text(self.gallery.settings['pwa_user']) - if hasattr(self.gallery, 'pwa_password'): - self.entry3.set_text(self.gallery.pwa_password) + self.user = self.gallery.settings['pwa_user'] + self.entry2.set_text(self.user) + if self.gallery.settings.has_key('pwa_pass'): + self.pwd = self.gallery.settings['pwa_pass'].get(self.user) + else: + self.gallery.settings['pwa_pass'] = {} + self.entry3.set_text(self.pwd) self.connect_signals() self.combobox2.set_property('visible', False) self.label5.set_property('visible', False) + self.label6.set_text('Username:') + if self.gallery.settings.has_key('pwa_pass'): + self.combobox3.handler_block(self.id3) + for key in sorted(self.gallery.settings['pwa_pass'].keys()): + self.boxmodel3.append([key]) + for row in self.boxmodel3: + if row[0] == self.user: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) + self.combobox3.handler_unblock(self.id3) + if self.gallery.window3.get_property('visible'): + self.window1.set_transient_for(self.gallery.window3) + elif self.gallery.window2.get_property('visible'): + self.window1.set_transient_for(self.gallery.window2) + else: + self.window1.set_transient_for(self.gallery.window1) def on_renderer_toggled(self, cell, path, model, position): model[path][position] = not model[path][position] @@ -118,6 +149,7 @@ self.window1.connect('key_press_event', self.on_key_press_event) self.radiobutton1.connect('toggled', self.on_radiobutton_toggled) self.button5.connect('clicked', self.change_user_account) + self.id3 = self.combobox3.connect('changed', self.change_user_account) def on_radiobutton_toggled(self, obj): self.label4.set_sensitive(not obj.get_active()) @@ -252,7 +284,7 @@ tags = row[4] or None if tags == '': tags = None - self.progressbar1.set_text('Uploading %s ...' % row[1]) + self.progressbar1.set_text(_('Uploading %s ...') % row[1]) self.model.set_value(row.iter, 5, 'uploading...') while gtk.events_pending(): gtk.main_iteration(False) @@ -300,6 +332,7 @@ def refresh_treeview(self): self.rawpath = {} + self.model.clear() for item in self.gallery.selected: source = os.path.basename(self.gallery.get_real_path(item)) self.rawpath[source] = item @@ -312,9 +345,8 @@ gtk.main_iteration(False) def refresh_pwa_albums(self, obj=None): - self.window1.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if self.pwa_login(): - self.label3.set_text('Retrieving albums...') + self.label3.set_text(_('Retrieving albums...')) while gtk.events_pending(): gtk.main_iteration(False) album_list = [] @@ -324,7 +356,7 @@ if album_list: self.populate_album_box(album_list) self.button2.set_sensitive(True) - self.label3.set_text('you have %s albums' % len(album_list)) + self.label3.set_text(_('you have %s albums') % len(album_list)) self.window1.window.set_cursor(None) while gtk.events_pending(): gtk.main_iteration(False) @@ -334,18 +366,25 @@ return False def change_user_account(self, obj): - self.gallery.pwa_password = '' + if obj == self.button5: + self.user = '' + self.pwd = '' + else: + self.user = obj.get_active_text() + self.pwd = self.gallery.settings['pwa_pass'].get(self.user) if not self.refresh_pwa_albums(): self.destroy(None) def pwa_login(self): - if hasattr(self.gallery, 'pwa_password') and self.gallery.pwa_password and self.gallery.settings.has_key('pwa_user') and self.gallery.settings['pwa_user']: - credentials = (self.gallery.settings['pwa_user'], self.gallery.pwa_password) + if self.user and self.pwd: + credentials = (self.user, self.pwd) else: credentials = self.dialog() if credentials: + self.window1.present() + self.progressbar1.hide() self.window1.window.set_cursor(self.gallery.watch) - self.label3.set_text('Authenticating...') + self.label3.set_text(_('Authenticating...')) while gtk.events_pending(): gtk.main_iteration(False) import gdata.photos.service @@ -355,13 +394,30 @@ self.client.password = credentials[1] self.client.source = 'GTKRawGallery' self.client.ProgrammaticLogin() - self.gallery.settings['pwa_user'] = self.entry2.get_text() - self.gallery.pwa_password = self.entry3.get_text() + self.gallery.settings['pwa_user'] = self.user = self.entry2.get_text() + self.gallery.settings['pwa_pass'][self.user] = self.pwd = self.entry3.get_text() + if self.gallery.settings.has_key('pwa_pass'): + self.combobox3.handler_block(self.id3) + for key in sorted(self.gallery.settings['pwa_pass'].keys()): + self.boxmodel3.append([key]) + for row in self.boxmodel3: + if row[0] == self.user: + self.combobox3.set_active_iter(row.iter) + break + else: + self.combobox3.set_active(0) return True + except service.BadAuthentication: + gtk.gdk.beep() + self.pwd = '' + self.label3.set_text(_('Invalid username or password!')) + self.window1.window.set_cursor(None) + self.pwa_login() except: - self.gallery.pwa_password = '' - self.label3.set_text('Log-in failed!') - print traceback.format_exc() + gtk.gdk.beep() + self.pwd = '' + self.label3.set_text(_('Log-in failed!')) + sys.stderr.write(traceback.format_exc()) self.window1.window.set_cursor(None) self.pwa_login() return None @@ -390,8 +446,6 @@ def main(self): self.gallery.window2.set_modal(False) - self.window1.show() - self.progressbar1.hide() self.refresh_treeview() if not self.refresh_pwa_albums(): self.destroy(None) @@ -407,6 +461,7 @@ if self.client: del self.client self.model.clear() + self.window1.destroy() return True diff -Nru gtkrawgallery-0.9.8/src/gtkrg_print.py gtkrawgallery-0.9.9/src/gtkrg_print.py --- gtkrawgallery-0.9.8/src/gtkrg_print.py 2013-03-21 04:03:40.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg_print.py 2013-09-24 08:49:56.000000000 +0000 @@ -63,16 +63,16 @@ bottom_margin = self.page_setup.get_bottom_margin(gtk.UNIT_MM) ratio = float(paper_h) / paper_w if ratio == 1: - cr_w = cr_h = 150 + cr_w = cr_h = 100 elif ratio < 1: - cr_w = 150 - cr_h = int(150 * ratio) + cr_w = 100 + cr_h = int(100* ratio) else: - cr_h = 210 + cr_h = 150 cr_w = int(cr_h / ratio) if cr_w > 150: - cr_w = 150 - cr_h = int(150 * ratio) + cr_w = 100 + cr_h = int(100 * ratio) self.p_drawingarea.set_size_request(cr_w + 2,cr_h + 2) cr.set_line_width(1) cr.rectangle(0.5, 0.5, cr_w, cr_h) @@ -136,6 +136,7 @@ def create_custom_widget(self, operation): tree = gtk.Builder() + tree.set_translation_domain('gtkrawgallery') tree.add_from_file(os.path.join(self.gtkrg_datadir, 'print.ui')) self.p_hbox1 = tree.get_object('hbox1') if sys.platform.startswith('win') and self.gallery.radiobutton13.get_active(): @@ -218,8 +219,7 @@ self.page_setup.set_orientation(gtk.PAGE_ORIENTATION_PORTRAIT) else: self.page_setup.set_orientation(gtk.PAGE_ORIENTATION_LANDSCAPE) - self.p_label3.set_text(_('%.1f x %.1f millimeters') % (self.page_setup.get_paper_width(gtk.UNIT_MM), \ - self.page_setup.get_paper_height(gtk.UNIT_MM))) + self.p_label3.set_text(_('%(w).1f x %(h).1f millimeters') % {'w':self.page_setup.get_paper_width(gtk.UNIT_MM), 'h':self.page_setup.get_paper_height(gtk.UNIT_MM)}) self.p_hbox1.unparent() return self.p_hbox1 @@ -331,8 +331,7 @@ self.p_spinbutton2.set_increments(step,0) self.p_spinbutton3.set_increments(step,0) self.p_spinbutton4.set_increments(step,0) - self.p_label3.set_text(_('%.3f x %.3f %s') % (self.page_setup.get_paper_width(unit), \ - self.page_setup.get_paper_height(unit), self.p_combobox2.get_active_text())) + self.p_label3.set_text(_('%(w).3f x %(h).3f %(text)s') % {'w':self.page_setup.get_paper_width(unit), 'h':self.page_setup.get_paper_height(unit), 'text':self.p_combobox2.get_active_text()}) self.p_spinbutton1.set_value(self.page_setup.get_left_margin(unit)) self.p_spinbutton2.set_value(self.page_setup.get_right_margin(unit)) self.p_spinbutton3.set_value(self.page_setup.get_top_margin(unit)) diff -Nru gtkrawgallery-0.9.8/src/gtkrg_profiles.py gtkrawgallery-0.9.9/src/gtkrg_profiles.py --- gtkrawgallery-0.9.8/src/gtkrg_profiles.py 2013-03-21 04:03:40.000000000 +0000 +++ gtkrawgallery-0.9.9/src/gtkrg_profiles.py 2013-09-24 08:49:56.000000000 +0000 @@ -640,6 +640,7 @@ fli_data = ([97, 48, 55, 98, 54, 56, 57, 51, 102, 50, 100, 50, 100, 102, 48, 102, 52, 52, 97, 98, 99, 102, 102, 56, 56, 101, 52, 53, 48, 54, 102, 48],[51, 57, 50, 101, 102, 100, 49, 48, 53, 52, 98, 102, 48, 54, 48, 50]) fb_data1 = [51, 48, 56, 52, 48, 55, 55, 50, 57, 49, 56, 51, 55, 50, 55] +dbx_data = ([98, 104, 53, 52, 115, 49, 118, 103, 120, 102, 104, 121, 57, 54, 106],[53, 103, 98, 104, 116, 111, 119, 122, 114, 115, 118, 102, 103, 97, 112]) Binary files /tmp/7qQ4u395Wo/gtkrawgallery-0.9.8/src/icons/Thumbs.db and /tmp/ycqBWFhi1G/gtkrawgallery-0.9.9/src/icons/Thumbs.db differ Binary files /tmp/7qQ4u395Wo/gtkrawgallery-0.9.8/src/icons/logo.ico and /tmp/ycqBWFhi1G/gtkrawgallery-0.9.9/src/icons/logo.ico differ diff -Nru gtkrawgallery-0.9.8/src/intl.py gtkrawgallery-0.9.9/src/intl.py --- gtkrawgallery-0.9.8/src/intl.py 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/intl.py 2013-09-24 08:49:56.000000000 +0000 @@ -0,0 +1,495 @@ +# -*- coding: utf-8 -*- +# +# Copyright © 2007-2010 Dieter Verfaillie +# +# This file is part of elib.intl. +# +# elib.intl is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# elib.intl is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with elib.intl. If not, see . + + +''' +The elib.intl module provides enhanced internationalization (I18N) services for +your Python modules and applications. + +elib.intl wraps Python's :func:`gettext` functionality and adds the following on +Microsoft Windows systems: + + - automatic detection of the current screen language (not necessarily the same + as the installation language) provided by MUI packs, + - makes sure internationalized C libraries which internally invoke gettext() or + dcgettext() can properly locate their message catalogs. This fixes a known + limitation in gettext's Windows support when using eg. gtk.builder or gtk.glade. + +See http://www.gnu.org/software/gettext/FAQ.html#windows_setenv for more +information. + +The elib.intl module defines the following functions: +''' + + +__all__ = ['install', 'install_module'] +__version__ = '0.0.3' +__docformat__ = 'restructuredtext' + + +import os +import sys +import locale +import gettext + +from logging import getLogger + + +logger = getLogger('elib.intl') + + +def _isofromlcid(lcid): + ''' + :param lcid: Microsoft Windows LCID + :returns: the ISO 639-1 language code for a given lcid. If there is no + ISO 639-1 language code assigned to the language specified by lcid, + the ISO 639-2 language code is returned. If the language specified + by lcid is unknown in the ISO 639-x database, None is returned. + + More information can be found on the following websites: + - List of ISO 639-1 and ISO 639-2 language codes: http://www.loc.gov/standards/iso639-2/ + - List of known lcid's: http://www.microsoft.com/globaldev/reference/lcid-all.mspx + - List of known MUI packs: http://www.microsoft.com/globaldev/reference/win2k/setup/Langid.mspx + ''' + mapping = {1078: 'af', #Afrikaans - South Africa + 1052: 'sq', #Albanian - Albania + 1118: 'am', #Amharic - Ethiopia + 1025: 'ar', #Arabic - Saudi Arabia + 5121: 'ar', #Arabic - Algeria + 15361: 'ar', #Arabic - Bahrain + 3073: 'ar', #Arabic - Egypt + 2049: 'ar', #Arabic - Iraq + 11265: 'ar', #Arabic - Jordan + 13313: 'ar', #Arabic - Kuwait + 12289: 'ar', #Arabic - Lebanon + 4097: 'ar', #Arabic - Libya + 6145: 'ar', #Arabic - Morocco + 8193: 'ar', #Arabic - Oman + 16385: 'ar', #Arabic - Qatar + 10241: 'ar', #Arabic - Syria + 7169: 'ar', #Arabic - Tunisia + 14337: 'ar', #Arabic - U.A.E. + 9217: 'ar', #Arabic - Yemen + 1067: 'hy', #Armenian - Armenia + 1101: 'as', #Assamese + 2092: 'az', #Azeri (Cyrillic) + 1068: 'az', #Azeri (Latin) + 1069: 'eu', #Basque + 1059: 'be', #Belarusian + 1093: 'bn', #Bengali (India) + 2117: 'bn', #Bengali (Bangladesh) + 5146: 'bs', #Bosnian (Bosnia/Herzegovina) + 1026: 'bg', #Bulgarian + 1109: 'my', #Burmese + 1027: 'ca', #Catalan + 1116: 'chr', #Cherokee - United States + 2052: 'zh', #Chinese - People's Republic of China + 4100: 'zh', #Chinese - Singapore + 1028: 'zh', #Chinese - Taiwan + 3076: 'zh', #Chinese - Hong Kong SAR + 5124: 'zh', #Chinese - Macao SAR + 1050: 'hr', #Croatian + 4122: 'hr', #Croatian (Bosnia/Herzegovina) + 1029: 'cs', #Czech + 1030: 'da', #Danish + 1125: 'dv', #Divehi + 1043: 'nl', #Dutch - Netherlands + 2067: 'nl', #Dutch - Belgium + 1126: 'bin', #Edo + 1033: 'en', #English - United States + 2057: 'en', #English - United Kingdom + 3081: 'en', #English - Australia + 10249: 'en', #English - Belize + 4105: 'en', #English - Canada + 9225: 'en', #English - Caribbean + 15369: 'en', #English - Hong Kong SAR + 16393: 'en', #English - India + 14345: 'en', #English - Indonesia + 6153: 'en', #English - Ireland + 8201: 'en', #English - Jamaica + 17417: 'en', #English - Malaysia + 5129: 'en', #English - New Zealand + 13321: 'en', #English - Philippines + 18441: 'en', #English - Singapore + 7177: 'en', #English - South Africa + 11273: 'en', #English - Trinidad + 12297: 'en', #English - Zimbabwe + 1061: 'et', #Estonian + 1080: 'fo', #Faroese + 1065: None, #TODO: Farsi + 1124: 'fil', #Filipino + 1035: 'fi', #Finnish + 1036: 'fr', #French - France + 2060: 'fr', #French - Belgium + 11276: 'fr', #French - Cameroon + 3084: 'fr', #French - Canada + 9228: 'fr', #French - Democratic Rep. of Congo + 12300: 'fr', #French - Cote d'Ivoire + 15372: 'fr', #French - Haiti + 5132: 'fr', #French - Luxembourg + 13324: 'fr', #French - Mali + 6156: 'fr', #French - Monaco + 14348: 'fr', #French - Morocco + 58380: 'fr', #French - North Africa + 8204: 'fr', #French - Reunion + 10252: 'fr', #French - Senegal + 4108: 'fr', #French - Switzerland + 7180: 'fr', #French - West Indies + 1122: 'fy', #Frisian - Netherlands + 1127: None, #TODO: Fulfulde - Nigeria + 1071: 'mk', #FYRO Macedonian + 2108: 'ga', #Gaelic (Ireland) + 1084: 'gd', #Gaelic (Scotland) + 1110: 'gl', #Galician + 1079: 'ka', #Georgian + 1031: 'de', #German - Germany + 3079: 'de', #German - Austria + 5127: 'de', #German - Liechtenstein + 4103: 'de', #German - Luxembourg + 2055: 'de', #German - Switzerland + 1032: 'el', #Greek + 1140: 'gn', #Guarani - Paraguay + 1095: 'gu', #Gujarati + 1128: 'ha', #Hausa - Nigeria + 1141: 'haw', #Hawaiian - United States + 1037: 'he', #Hebrew + 1081: 'hi', #Hindi + 1038: 'hu', #Hungarian + 1129: None, #TODO: Ibibio - Nigeria + 1039: 'is', #Icelandic + 1136: 'ig', #Igbo - Nigeria + 1057: 'id', #Indonesian + 1117: 'iu', #Inuktitut + 1040: 'it', #Italian - Italy + 2064: 'it', #Italian - Switzerland + 1041: 'ja', #Japanese + 1099: 'kn', #Kannada + 1137: 'kr', #Kanuri - Nigeria + 2144: 'ks', #Kashmiri + 1120: 'ks', #Kashmiri (Arabic) + 1087: 'kk', #Kazakh + 1107: 'km', #Khmer + 1111: 'kok', #Konkani + 1042: 'ko', #Korean + 1088: 'ky', #Kyrgyz (Cyrillic) + 1108: 'lo', #Lao + 1142: 'la', #Latin + 1062: 'lv', #Latvian + 1063: 'lt', #Lithuanian + 1086: 'ms', #Malay - Malaysia + 2110: 'ms', #Malay - Brunei Darussalam + 1100: 'ml', #Malayalam + 1082: 'mt', #Maltese + 1112: 'mni', #Manipuri + 1153: 'mi', #Maori - New Zealand + 1102: 'mr', #Marathi + 1104: 'mn', #Mongolian (Cyrillic) + 2128: 'mn', #Mongolian (Mongolian) + 1121: 'ne', #Nepali + 2145: 'ne', #Nepali - India + 1044: 'no', #Norwegian (Bokmᅢᆬl) + 2068: 'no', #Norwegian (Nynorsk) + 1096: 'or', #Oriya + 1138: 'om', #Oromo + 1145: 'pap', #Papiamentu + 1123: 'ps', #Pashto + 1045: 'pl', #Polish + 1046: 'pt', #Portuguese - Brazil + 2070: 'pt', #Portuguese - Portugal + 1094: 'pa', #Punjabi + 2118: 'pa', #Punjabi (Pakistan) + 1131: 'qu', #Quecha - Bolivia + 2155: 'qu', #Quecha - Ecuador + 3179: 'qu', #Quecha - Peru + 1047: 'rm', #Rhaeto-Romanic + 1048: 'ro', #Romanian + 2072: 'ro', #Romanian - Moldava + 1049: 'ru', #Russian + 2073: 'ru', #Russian - Moldava + 1083: 'se', #Sami (Lappish) + 1103: 'sa', #Sanskrit + 1132: 'nso', #Sepedi + 3098: 'sr', #Serbian (Cyrillic) + 2074: 'sr', #Serbian (Latin) + 1113: 'sd', #Sindhi - India + 2137: 'sd', #Sindhi - Pakistan + 1115: 'si', #Sinhalese - Sri Lanka + 1051: 'sk', #Slovak + 1060: 'sl', #Slovenian + 1143: 'so', #Somali + 1070: 'wen', #Sorbian + 3082: 'es', #Spanish - Spain (Modern Sort) + 1034: 'es', #Spanish - Spain (Traditional Sort) + 11274: 'es', #Spanish - Argentina + 16394: 'es', #Spanish - Bolivia + 13322: 'es', #Spanish - Chile + 9226: 'es', #Spanish - Colombia + 5130: 'es', #Spanish - Costa Rica + 7178: 'es', #Spanish - Dominican Republic + 12298: 'es', #Spanish - Ecuador + 17418: 'es', #Spanish - El Salvador + 4106: 'es', #Spanish - Guatemala + 18442: 'es', #Spanish - Honduras + 58378: 'es', #Spanish - Latin America + 2058: 'es', #Spanish - Mexico + 19466: 'es', #Spanish - Nicaragua + 6154: 'es', #Spanish - Panama + 15370: 'es', #Spanish - Paraguay + 10250: 'es', #Spanish - Peru + 20490: 'es', #Spanish - Puerto Rico + 21514: 'es', #Spanish - United States + 14346: 'es', #Spanish - Uruguay + 8202: 'es', #Spanish - Venezuela + 1072: None, #TODO: Sutu + 1089: 'sw', #Swahili + 1053: 'sv', #Swedish + 2077: 'sv', #Swedish - Finland + 1114: 'syr', #Syriac + 1064: 'tg', #Tajik + 1119: None, #TODO: Tamazight (Arabic) + 2143: None, #TODO: Tamazight (Latin) + 1097: 'ta', #Tamil + 1092: 'tt', #Tatar + 1098: 'te', #Telugu + 1054: 'th', #Thai + 2129: 'bo', #Tibetan - Bhutan + 1105: 'bo', #Tibetan - People's Republic of China + 2163: 'ti', #Tigrigna - Eritrea + 1139: 'ti', #Tigrigna - Ethiopia + 1073: 'ts', #Tsonga + 1074: 'tn', #Tswana + 1055: 'tr', #Turkish + 1090: 'tk', #Turkmen + 1152: 'ug', #Uighur - China + 1058: 'uk', #Ukrainian + 1056: 'ur', #Urdu + 2080: 'ur', #Urdu - India + 2115: 'uz', #Uzbek (Cyrillic) + 1091: 'uz', #Uzbek (Latin) + 1075: 've', #Venda + 1066: 'vi', #Vietnamese + 1106: 'cy', #Welsh + 1076: 'xh', #Xhosa + 1144: 'ii', #Yi + 1085: 'yi', #Yiddish + 1130: 'yo', #Yoruba + 1077: 'zu'} #Zulu + + return mapping[lcid] + +def _getscreenlanguage(): + ''' + :returns: the ISO 639-x language code for this session. + + If the LANGUAGE environment variable is set, it's value overrides the + screen language detection. Otherwise the screen language is determined by + the currently selected Microsoft Windows MUI language pack or the Microsoft + Windows installation language. + + Works on Microsoft Windows 2000 and up. + ''' + if sys.platform == 'win32' or sys.platform == 'nt': + # Start with nothing + lang = None + + # Check the LANGUAGE environment variable + lang = os.getenv('LANGUAGE') + + if lang is None: + # Start with nothing + lcid = None + + try: + from ctypes import windll + lcid = windll.kernel32.GetUserDefaultUILanguage() + except: + logger.debug('Failed to get current screen language with \'GetUserDefaultUILanguage\'') + finally: + if lcid is None: + lang = 'C' + else: + lang = _isofromlcid(lcid) + + logger.debug('Windows screen language is \'%s\' (lcid %s)' % (lang, lcid)) + + return lang + +def _putenv(name, value): + ''' + :param name: environment variable name + :param value: environment variable value + + This function ensures that changes to an environment variable are applied + to each copy of the environment variables used by a process. Starting from + Python 2.4, os.environ changes only apply to the copy Python keeps (os.environ) + and are no longer automatically applied to the other copies for the process. + + On Microsoft Windows, each process has multiple copies of the environment + variables, one managed by the OS and one managed by the C library. We also + need to take care of the fact that the C library used by Python is not + necessarily the same as the C library used by pygtk and friends. This because + the latest releases of pygtk and friends are built with mingw32 and are thus + linked against msvcrt.dll. The official gtk+ binaries have always been built + in this way. + ''' + if sys.platform == 'win32' or sys.platform == 'nt': + from ctypes import windll + from ctypes import cdll + from ctypes.util import find_msvcrt + + # Update Python's copy of the environment variables + os.environ[name] = value + + # Update the copy maintained by Windows (so SysInternals Process Explorer sees it) + try: + result = windll.kernel32.SetEnvironmentVariableW(name, value) + if result == 0: raise Warning + except Exception: + logger.debug('Failed to set environment variable \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % name) + else: + logger.debug('Set environment variable \'%s\' to \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % (name, value)) + + # Update the copy maintained by msvcrt (used by gtk+ runtime) + try: + result = cdll.msvcrt._putenv('%s=%s' % (name, value)) + if result !=0: raise Warning + except Exception: + logger.debug('Failed to set environment variable \'%s\' (\'msvcrt._putenv\')' % name) + else: + logger.debug('Set environment variable \'%s\' to \'%s\' (\'msvcrt._putenv\')' % (name, value)) + + # Update the copy maintained by whatever c runtime is used by Python + try: + msvcrt = find_msvcrt() + msvcrtname = str(msvcrt).split('.')[0] if '.' in msvcrt else str(msvcrt) + result = cdll.LoadLibrary(msvcrt)._putenv('%s=%s' % (name, value)) + if result != 0: raise Warning + except Exception: + logger.debug('Failed to set environment variable \'%s\' (\'%s._putenv\')' % (name, msvcrtname)) + else: + logger.debug('Set environment variable \'%s\' to \'%s\' (\'%s._putenv\')' % (name, value, msvcrtname)) + + +def _dugettext(domain, message): + ''' + :param domain: translation domain + :param message: message to translate + :returns: the translated message + + Unicode version of :func:`gettext.dgettext`. + ''' + try: + t = gettext.translation(domain, gettext._localedirs.get(domain, None), + codeset=gettext._localecodesets.get(domain)) + except IOError: + return message + else: + return t.ugettext(message) + +def _install(domain, localedir, asglobal=False, lang=None): + ''' + :param domain: translation domain + :param localedir: locale directory + :param asglobal: if True, installs the function _() in Python’s builtin namespace. Default is False + + Private function doing all the work for the :func:`elib.intl.install` and + :func:`elib.intl.install_module` functions. + ''' + # prep locale system + if asglobal: + locale.setlocale(locale.LC_ALL, '') + + # on windows systems, set the LANGUAGE environment variable + if sys.platform == 'win32' or sys.platform == 'nt': + _putenv('LANGUAGE', lang or _getscreenlanguage()) + + # The locale module on Max OS X lacks bindtextdomain so we specifically + # test on linux2 here. See commit 4ae8b26fd569382ab66a9e844daa0e01de409ceb + if sys.platform == 'linux2': + locale.bindtextdomain(domain, localedir) + locale.bind_textdomain_codeset(domain, 'UTF-8') + locale.textdomain(domain) + + # initialize Python's gettext interface + gettext.bindtextdomain(domain, localedir) + gettext.bind_textdomain_codeset(domain, 'UTF-8') + + if asglobal: + gettext.textdomain(domain) + + # on windows systems, initialize libintl + if sys.platform == 'win32' or sys.platform == 'nt': + from ctypes import cdll + libintl = cdll.intl + libintl.bindtextdomain(domain, localedir) + libintl.bind_textdomain_codeset(domain, 'UTF-8') + + if asglobal: + libintl.textdomain(domain) + + del libintl + +def install(domain, localedir, lang=None): + ''' + :param domain: translation domain + :param localedir: locale directory + + Installs the function _() in Python’s builtin namespace, based on + domain and localedir. Codeset is always UTF-8. + + As seen below, you usually mark the strings in your application that are + candidates for translation, by wrapping them in a call to the _() function, + like this: + + .. sourcecode:: python + + import elib.intl + elib.intl.install('myapplication', '/path/to/usr/share/locale') + print _('This string will be translated.') + + Note that this is only one way, albeit the most convenient way, + to make the _() function available to your application. Because it affects + the entire application globally, and specifically Python’s built-in + namespace, localized modules should never install _(). Instead, you should + use :func:`elib.intl.install_module` to make _() available to your module. + ''' + _install(domain, localedir, True, lang) + gettext.install(domain, localedir, unicode=True) + +def install_module(domain, localedir): + ''' + :param domain: translation domain + :param localedir: locale directory + :returns: an anonymous function object, based on domain and localedir. + Codeset is always UTF-8. + + You may find this function usefull when writing localized modules. + Use this code to make _() available to your module: + + .. sourcecode:: python + + import elib.intl + _ = elib.intl.install_module('mymodule', '/path/to/usr/share/locale') + print _('This string will be translated.') + + When writing a package, you can usually do this in the package's __init__.py + file and import the _() function from the package namespace as needed. + ''' + _install(domain, localedir, False) + return lambda message: _dugettext(domain, message) diff -Nru gtkrawgallery-0.9.8/src/it.po gtkrawgallery-0.9.9/src/it.po --- gtkrawgallery-0.9.8/src/it.po 1970-01-01 00:00:00.000000000 +0000 +++ gtkrawgallery-0.9.9/src/it.po 2013-09-08 07:06:44.000000000 +0000 @@ -0,0 +1,3042 @@ +# Italian translations for gtkrawgallery package. +# Copyright (C) 2013 THE gtkrawgallery'S COPYRIGHT HOLDER +# This file is distributed under the same license as the gtkrawgallery package. +# daniele , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: gtkrawgallery 0.9.9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-09-08 09:04+0200\n" +"PO-Revision-Date: 2013-09-08 09:06+0100\n" +"Last-Translator: Daniele Isca \n" +"Language-Team: Italian \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.5.5\n" + +#: ../gtkrawgallery.py:268 ../gtkrawgallery.py:4352 ../gtkrawgallery.py:5149 +msgid "Original" +msgstr "Originale" + +#: ../gtkrawgallery.py:373 gtkrg.ui.h:24 +msgid "Albums" +msgstr "Album" + +#: ../gtkrawgallery.py:387 +msgid "Directory Tree" +msgstr "Albero delle cartelle" + +#: ../gtkrawgallery.py:466 ../gtkrawgallery.py:480 gtkrg.ui.h:176 +msgid "Keywords" +msgstr "Etichette" + +#: ../gtkrawgallery.py:528 gtkrg.ui.h:403 +msgid "White" +msgstr "Bianco" + +#: ../gtkrawgallery.py:529 +msgid "Grey" +msgstr "Grigio" + +#: ../gtkrawgallery.py:530 gtkrg.ui.h:41 +msgid "Black" +msgstr "Nero" + +#: ../gtkrawgallery.py:569 +msgid "Intersection" +msgstr "Intersezione" + +#: ../gtkrawgallery.py:570 +msgid "Union" +msgstr "Unione" + +#: ../gtkrawgallery.py:593 +msgid "Keep embedded profile as input profile" +msgstr "Imposta il profilo incorporato come profilo di input" + +#: ../gtkrawgallery.py:594 +msgid "Ignore embedded profile" +msgstr "Ignora profilo incorporato" + +#: ../gtkrawgallery.py:595 +msgid "Ask always" +msgstr "Chiedi sempre" + +#: ../gtkrawgallery.py:604 ../gtkrawgallery.py:801 gtkrg.ui.h:354 +msgid "Tags" +msgstr "Etichette" + +#: ../gtkrawgallery.py:631 ../gtkrawgallery.py:736 +msgid "Preview" +msgstr "Anteprima" + +#: ../gtkrawgallery.py:634 +msgid "Attachment" +msgstr "Allegato" + +#: ../gtkrawgallery.py:645 ../gtkrawgallery.py:662 ../gtkrawgallery.py:679 +#: ../gtkrawgallery.py:696 gtkrg.ui.h:127 +msgid "Favorites" +msgstr "Favoriti" + +#: ../gtkrawgallery.py:732 +msgid "Enabled" +msgstr "Abilitato" + +#: ../gtkrawgallery.py:755 +msgid "WB Name" +msgstr "Nome WB" + +#: ../gtkrawgallery.py:762 +msgid "Red multiplier" +msgstr "Moltiplicatore rosso" + +#: ../gtkrawgallery.py:769 +msgid "Green multiplier" +msgstr "Moltiplicatore verde" + +#: ../gtkrawgallery.py:776 +msgid "Blue multiplier" +msgstr "Moltiplicatore blu" + +#: ../gtkrawgallery.py:790 +msgid "workflow history" +msgstr "Cronologia" + +#: ../gtkrawgallery.py:1145 +msgid "Sound files" +msgstr "File audio" + +#: ../gtkrawgallery.py:1146 +msgid "All known images" +msgstr "Tutti i formati conosciuti" + +#: ../gtkrawgallery.py:1147 +msgid "ICC Profile" +msgstr "Profilo ICC" + +#: ../gtkrawgallery.py:1148 +msgid "GDKPixbuf known images" +msgstr "Formati supportati da GDKPixbuf" + +#: ../gtkrawgallery.py:1149 +msgid "Raw images" +msgstr "Solo immagini raw" + +#: ../gtkrawgallery.py:1183 ../gtkrawgallery.py:3455 +msgid "Disconnect GUI" +msgstr "Disconnetti GUI" + +#: ../gtkrawgallery.py:1225 +msgid "Hide Sidebar" +msgstr "Nascondi barra laterale" + +#: ../gtkrawgallery.py:1235 +msgid "Enter one or more tags separated by comma or semicolon" +msgstr "Inserisci una o più etichette separate da una virgola" + +#: ../gtkrawgallery.py:2596 ../gtkrawgallery.py:8150 gtkrg.ui.h:159 +msgid "Info!" +msgstr "Informazioni" + +#: ../gtkrawgallery.py:2596 +msgid "No file selected!" +msgstr "Nessun file selezionato!" + +#: ../gtkrawgallery.py:2693 +msgid "Cannot read profile!" +msgstr "Errore di lettura profilo!" + +#: ../gtkrawgallery.py:2896 +msgid "Insert a name" +msgstr "Inserire il nome" + +#: ../gtkrawgallery.py:2897 +msgid "Save WB Presets" +msgstr "Salva preset WB" + +#: ../gtkrawgallery.py:2919 ../gtkrawgallery.py:3920 ../gtkrawgallery.py:4869 +#: ../gtkrawgallery.py:6264 ../gtkrawgallery.py:7755 ../gtkrawgallery.py:8060 +#: ../gtkrawgallery.py:8160 ../gtkrawgallery.py:8782 ../gtkrawgallery.py:8915 +#: ../gtkrawgallery.py:9577 ../gtkrawgallery.py:13406 +#: ../gtkrawgallery.py:13536 ../gtkrawgallery.py:13612 +#: ../gtkrawgallery.py:14957 gtkrg.ui.h:33 +msgid "Attention!" +msgstr "Attenzione!" + +#: ../gtkrawgallery.py:2919 +msgid "WB Name missing!" +msgstr "Nome WB mancante!" + +#: ../gtkrawgallery.py:2979 ../gtkrawgallery.py:3216 +msgid "loading previews..." +msgstr "Caricamento anteprime..." + +#: ../gtkrawgallery.py:3000 ../gtkrawgallery.py:3015 ../gtkrawgallery.py:3232 +#: ../gtkrawgallery.py:8701 ../gtkrawgallery.py:8712 +#, python-format +msgid "Total size: %.3f Mb" +msgstr "Spazio totale: %.3f Mb" + +#: ../gtkrawgallery.py:3039 +msgid "" +"This panel permits to edit a custom metadata list.\n" +"1) select a group;\n" +"2) select a tag;\n" +"3) click \"add to list\";\n" +"Repeat the previous three points to add other tags;\n" +"4) Double click on the value column to edit;\n" +"5) Click the \"write metadata\" button.\n" +"You can save the list as template." +msgstr "" +"Questo strumento permette la creazione di una lista personalizzata di " +"metadati.\n" +"1) selezionare un gruppo;\n" +"2) selezionare un'etichetta;\n" +"3) premere il pulsante \"aggiungi alla lista\";\n" +"Ripetere i tre punti precedenti per aggiungere altre etichette;\n" +"4) fare doppio clic sul campo value per editare;\n" +"5) premere il pulsante \"scrivi metadati\".\n" +"E' possibile salvare un template della lista." + +#: ../gtkrawgallery.py:3045 +msgid "About Advanced Metadata Editor" +msgstr "Informazioni sull'Editor avanzato dei metadati" + +#: ../gtkrawgallery.py:3060 +msgid "No device found. Burn disabled!" +msgstr "Dispositivo non trovato. Scrittura disco disabilitata!" + +#: ../gtkrawgallery.py:3080 +msgid "Device not found!" +msgstr "Dispositivo non trovato!" + +#: ../gtkrawgallery.py:3097 +msgid "creating disk image..." +msgstr "creazione immagine disco..." + +#: ../gtkrawgallery.py:3129 +msgid "starting cdrecord..." +msgstr "avvio cdrecord..." + +#: ../gtkrawgallery.py:3139 +msgid "Cannot load media!" +msgstr "Errore caricamento CD!" + +#: ../gtkrawgallery.py:3176 +msgid "" +"Cdrecord Error!\n" +"Read debugging output for more info!" +msgstr "" +"Errore Cdrecord!\n" +"Per maggiori informazioni leggere l'output di debug!" + +#: ../gtkrawgallery.py:3178 +msgid "Disk successfully burned!" +msgstr "Scrittura disco terminata con successo!" + +#: ../gtkrawgallery.py:3264 +msgid "Server missing!" +msgstr "Nome server mancante!" + +#: ../gtkrawgallery.py:3270 +msgid "User missing!" +msgstr "User mancante!" + +#: ../gtkrawgallery.py:3278 +msgid "Password missing!" +msgstr "Password mancante!" + +#: ../gtkrawgallery.py:3288 +msgid "Sender address missing!" +msgstr "Indirizzo mittente mancante!" + +#: ../gtkrawgallery.py:3293 +msgid "Destination address missing!" +msgstr "Indirizzo destinatario mancante!" + +#: ../gtkrawgallery.py:3298 +msgid "Subject missing!" +msgstr "Oggetto mancante!" + +#: ../gtkrawgallery.py:3310 +msgid "connecting..." +msgstr "connessione in corso..." + +#: ../gtkrawgallery.py:3319 +msgid "connection failed!" +msgstr "connessione fallita!" + +#: ../gtkrawgallery.py:3339 +msgid "sending..." +msgstr "invio in corso..." + +#: ../gtkrawgallery.py:3345 +msgid "Mail successfully sent!" +msgstr "Posta inviata con successo!" + +#: ../gtkrawgallery.py:3355 ../gtkrawgallery.py:10806 +msgid "Error!" +msgstr "Errore!" + +#: ../gtkrawgallery.py:3383 +msgid "Processing Image..." +msgstr "Elaborazione Immagine..." + +#: ../gtkrawgallery.py:3443 +msgid "" +"A photo manager\n" +"for digital camera raw image development." +msgstr "" +"Gestore di fotografie per lo sviluppo\n" +"delle immagini raw delle fotocamere digitali." + +#: ../gtkrawgallery.py:3447 +msgid "Visit the Project Page" +msgstr "Visita la pagina del progetto" + +#: ../gtkrawgallery.py:3460 +msgid "Connect GUI" +msgstr "Connetti GUI" + +#: ../gtkrawgallery.py:3595 +msgid "save raw style" +msgstr "salva stile raw" + +#: ../gtkrawgallery.py:3597 ../gtkrawgallery.py:5065 +msgid "Insert a style name" +msgstr "Inserisci un nome per lo stile" + +#: ../gtkrawgallery.py:3623 +#, python-format +msgid "Save %s changes?" +msgstr "Salvare modifiche in %s?" + +#: ../gtkrawgallery.py:3624 +msgid "Save raw style" +msgstr "Salva stile raw" + +#: ../gtkrawgallery.py:3649 ../gtkrawgallery.py:7285 ../gtkrawgallery.py:10828 +#, python-format +msgid "" +"Are you sure to delete\n" +" %s ?" +msgstr "" +"Vuoi eliminare\n" +" %s?" + +#: ../gtkrawgallery.py:3650 +msgid "Delete raw style" +msgstr "Elimina stile raw" + +#: ../gtkrawgallery.py:3720 ../gtkrawgallery.py:8341 ../gtkrawgallery.py:8362 +#: ../gtkrawgallery.py:8378 ../gtkrawgallery.py:8401 ../gtkrawgallery.py:8795 +#: ../gtkrawgallery.py:8829 +msgid "Updating Metadata..." +msgstr "Aggiornamento metadati..." + +#: ../gtkrawgallery.py:3729 ../gtkrawgallery.py:12883 +msgid "Saving to album..." +msgstr "Sto salvando nell'album..." + +#: ../gtkrawgallery.py:3920 ../gtkrawgallery.py:4869 +msgid "" +"No workflow to save.\n" +"Please, make a new one!" +msgstr "" +"Nessun workflow da salvare.\n" +"Iniziare un nuovo workflow!" + +#: ../gtkrawgallery.py:4578 +msgid "Channel mixer" +msgstr "Mixer canali" + +#: ../gtkrawgallery.py:4756 +msgid "Levels" +msgstr "Livelli" + +#: ../gtkrawgallery.py:4772 +msgid "Brightness and saturation" +msgstr "Luminosità e saturazione" + +#: ../gtkrawgallery.py:4792 +msgid "Color balance" +msgstr "Bilanciamento colore" + +#: ../gtkrawgallery.py:4808 gtkrg.ui.h:79 +msgid "Contrast" +msgstr "Contrasto" + +#: ../gtkrawgallery.py:4825 +msgid "Sigmoidal contrast" +msgstr "Contrasto dei toni medi" + +#: ../gtkrawgallery.py:4952 ../gtkrawgallery.py:7362 +msgid "highlights" +msgstr "Alte luci" + +#: ../gtkrawgallery.py:4954 ../gtkrawgallery.py:7364 +msgid "shadows" +msgstr "Ombre" + +#: ../gtkrawgallery.py:5063 gtkrg.ui.h:462 +msgid "save style" +msgstr "salva stile" + +#: ../gtkrawgallery.py:5149 +msgid "Style " +msgstr "Stile " + +#: ../gtkrawgallery.py:5166 +#, python-format +msgid "" +"Are you sure to delete the style\n" +" %s ?" +msgstr "" +"Vuoi eliminare lo stile\n" +"%s?" + +#: ../gtkrawgallery.py:5167 +msgid "Delete style" +msgstr "Elimina stile" + +#: ../gtkrawgallery.py:6161 gtkrg.ui.h:313 +msgid "Scale" +msgstr "Scala" + +#: ../gtkrawgallery.py:6181 +msgid "Flip" +msgstr "Ribalta verticalmente" + +#: ../gtkrawgallery.py:6185 +msgid "Flop" +msgstr "Ribalta orizzontalmente" + +#: ../gtkrawgallery.py:6189 +msgid "Rotate 90" +msgstr "Ruota a destra" + +#: ../gtkrawgallery.py:6193 +msgid "Rotate -90" +msgstr "Ruota a sinistra" + +#: ../gtkrawgallery.py:6230 gtkrg.ui.h:46 +msgid "Border" +msgstr "Bordo" + +#: ../gtkrawgallery.py:6258 +msgid "Fine rotation" +msgstr "Rotazione fine" + +#: ../gtkrawgallery.py:6264 +msgid "You must select a rectangle first!" +msgstr "Devi selezionare un rettangolo prima!" + +#: ../gtkrawgallery.py:6314 gtkrg.ui.h:82 +msgid "Crop" +msgstr "Ritaglia" + +#: ../gtkrawgallery.py:6359 +msgid "Sharpening..." +msgstr "elaborazione affilatura..." + +#: ../gtkrawgallery.py:6390 gtkrg.ui.h:328 +msgid "Sharpen" +msgstr "Affilatura" + +#: ../gtkrawgallery.py:6398 +msgid "Unsharp Mask..." +msgstr "elaborazione maschera di contrasto..." + +#: ../gtkrawgallery.py:6430 +msgid "Unsharp mask" +msgstr "Maschera di contrasto" + +#: ../gtkrawgallery.py:6438 +msgid "Blurring..." +msgstr "elaborazione sfocatura..." + +#: ../gtkrawgallery.py:6490 gtkrg.ui.h:45 +msgid "Blur" +msgstr "Sfuma" + +#: ../gtkrawgallery.py:6498 +msgid "Charcoal..." +msgstr "applico carboncino..." + +#: ../gtkrawgallery.py:6527 gtkrg.ui.h:57 +msgid "Charcoal" +msgstr "Carboncino" + +#: ../gtkrawgallery.py:6537 +msgid "Sketch..." +msgstr "elaborazione schizzo..." + +#: ../gtkrawgallery.py:6567 gtkrg.ui.h:337 +msgid "Sketch" +msgstr "Schizzo" + +#: ../gtkrawgallery.py:6578 +msgid "Posterize..." +msgstr "elaborazione posterizzazione..." + +#: ../gtkrawgallery.py:6606 gtkrg.ui.h:267 +msgid "Posterize" +msgstr "Posterizza" + +#: ../gtkrawgallery.py:6616 +msgid "Negate..." +msgstr "elaborazione negativo..." + +#: ../gtkrawgallery.py:6644 gtkrg.ui.h:207 +msgid "Negate" +msgstr "Negativo" + +#: ../gtkrawgallery.py:6654 +msgid "Noising..." +msgstr "elaborazione aggiungi rumore..." + +#: ../gtkrawgallery.py:6682 +msgid "Add noise" +msgstr "Aggiungi rumore" + +#: ../gtkrawgallery.py:6690 +msgid "Denoising..." +msgstr "elaborazione riduzione rumore..." + +#: ../gtkrawgallery.py:6719 +msgid "Reduce noise" +msgstr "Riduzione rumore" + +#: ../gtkrawgallery.py:6727 +msgid "Median Filter..." +msgstr "elaborazione filtro mediano..." + +#: ../gtkrawgallery.py:6756 +msgid "Median filter" +msgstr "Filtro mediano" + +#: ../gtkrawgallery.py:6764 +msgid "Sepia Tone..." +msgstr "elaborazione tonalità seppia..." + +#: ../gtkrawgallery.py:6793 +msgid "Sepia tone" +msgstr "Tono seppia" + +#: ../gtkrawgallery.py:6820 +msgid "Grayscale" +msgstr "Scala di grigi" + +#: ../gtkrawgallery.py:6853 +msgid "Vignette removal" +msgstr "Rimozione vignettatura" + +#: ../gtkrawgallery.py:6864 gtkrg.ui.h:247 +msgid "Normalize" +msgstr "Normalizza" + +#: ../gtkrawgallery.py:6895 +msgid "Distort" +msgstr "Distorsione" + +#: ../gtkrawgallery.py:6977 +msgid "Red eye removal" +msgstr "Rimozione occhi rossi" + +#: ../gtkrawgallery.py:6988 +msgid "Oil Paint..." +msgstr "elaborazione pittura a olio..." + +#: ../gtkrawgallery.py:7017 +msgid "Oil paint" +msgstr "Pittura a olio" + +#: ../gtkrawgallery.py:7025 +msgid "Spread..." +msgstr "elaborazione spruzzo..." + +#: ../gtkrawgallery.py:7054 gtkrg.ui.h:341 +msgid "Spread" +msgstr "Spruzzo" + +#: ../gtkrawgallery.py:7062 +msgid "Despeckling..." +msgstr "elaborazione rimozione rumore a macchie..." + +#: ../gtkrawgallery.py:7088 +msgid "Despeckle" +msgstr "Rimozione rumore a macchie" + +#: ../gtkrawgallery.py:7245 +msgid "Curve" +msgstr "Curve" + +#: ../gtkrawgallery.py:7257 +msgid "save curve" +msgstr "salva curva" + +#: ../gtkrawgallery.py:7259 gtkrg.ui.h:162 +msgid "Insert a curve name" +msgstr "Inserire il nome della curva" + +#: ../gtkrawgallery.py:7286 +msgid "Delete curve preset" +msgstr "Elimina preset curva" + +#: ../gtkrawgallery.py:7480 gtkrg.ui.h:385 +msgid "Tint" +msgstr "Tinta" + +#: ../gtkrawgallery.py:7624 +msgid "White balance" +msgstr "Bilanciamento del bianco" + +#: ../gtkrawgallery.py:7654 +msgid "Color temperature" +msgstr "Temperatura colore" + +#: ../gtkrawgallery.py:7741 +msgid "The file was added to the batch queue!" +msgstr "il file è stato aggiunto alla coda del processore batch!" + +#: ../gtkrawgallery.py:7755 +msgid "Cannot add twin items!" +msgstr "Il file esiste già!" + +#: ../gtkrawgallery.py:7921 +#, python-format +msgid "Batch Manager - processing file %(run)s - remaining %(rem)s" +msgstr "Batch Manager - elaborazione file %(run)s - rimanenti %(rem)s" + +#: ../gtkrawgallery.py:7925 +msgid "completed" +msgstr "completato" + +#: ../gtkrawgallery.py:7936 +msgid "Batch Manager - Queue completed!" +msgstr "Batch Manager - Coda completata!" + +#: ../gtkrawgallery.py:8060 +msgid "" +"Add preferred tags to list first!\n" +"Double click the value row to edit." +msgstr "" +"Aggiungere prima le etichette alla lista!\n" +"Fai doppio clic sul campo valore per editare." + +#: ../gtkrawgallery.py:8069 +msgid "Template name" +msgstr "nome template" + +#: ../gtkrawgallery.py:8070 gtkrg.ui.h:312 +msgid "Save metadata template" +msgstr "Salva template metadati" + +#: ../gtkrawgallery.py:8103 +msgid "Operation not permitted!" +msgstr "Operazione non permessa!" + +#: ../gtkrawgallery.py:8150 +msgid "Metadata was written!" +msgstr "Metadati scritti con successo!" + +#: ../gtkrawgallery.py:8159 +msgid "" +"Are you sure to clear\n" +"the tags database?" +msgstr "" +"Vuoi cancellare tutti i dati\n" +"dal database delle etichette?" + +#: ../gtkrawgallery.py:8311 +#, python-format +msgid "" +"Exiftool cannot write %(name_)s\n" +"%(output_)s" +msgstr "" +"Exiftool: errore scrittura di %(name_)s\n" +"%(output_)s" + +#: ../gtkrawgallery.py:8781 +#, python-format +msgid "Remove tag \"%s\" ?" +msgstr "Rimuovere etichetta \"%s\"?" + +#: ../gtkrawgallery.py:8818 +msgid "New tag name" +msgstr "Nuovo nome etichetta" + +#: ../gtkrawgallery.py:8819 gtkrg.ui.h:291 +msgid "Rename tag" +msgstr "Rinomina etichetta" + +#: ../gtkrawgallery.py:8915 +msgid "Restore default settings?" +msgstr "Ripristino impostazioni predefinite?" + +#: ../gtkrawgallery.py:9025 ../gtkrawgallery.py:9037 ../gtkrawgallery.py:9048 +#, python-format +msgid "Unknown file: %s" +msgstr "File sconosciuto: %s" + +#: ../gtkrawgallery.py:9110 +msgid "Embedded Profile" +msgstr "Profilo incorporato" + +#: ../gtkrawgallery.py:9467 +#, python-format +msgid "Convert Selection (%d files)" +msgstr "Converto selezione (%d file)" + +#: ../gtkrawgallery.py:9562 +#, python-format +msgid "Cannot convert %s" +msgstr "Errore conversione di %s" + +#: ../gtkrawgallery.py:9576 +#, python-format +msgid "" +"A file named:\n" +"\"%s\" already exists.\n" +"Do you want to overwrite it?" +msgstr "" +"Un file di nome:\n" +"\"%s\" esiste già.\n" +"Vuoi sovrascriverlo?" + +#: ../gtkrawgallery.py:10072 ../gtkrawgallery.py:10115 +msgid "Saving..." +msgstr "Salvataggio in corso..." + +#: ../gtkrawgallery.py:10170 ../gtkrawgallery.py:10219 +#: ../gtkrawgallery.py:10268 ../gtkrawgallery.py:10317 +#, python-format +msgid "Converting %(n)s of %(sel)s files..." +msgstr "Converto %(n)s of %(sel)s file..." + +#: ../gtkrawgallery.py:10401 +#, python-format +msgid "% of " +msgstr "% di " + +#: ../gtkrawgallery.py:10463 ../gtkrawgallery.py:10576 +#: ../gtkrawgallery.py:10744 ../gtkrawgallery.py:12846 +#: ../gtkrawgallery.py:13144 ../gtkrawgallery.py:13380 +#: ../gtkrawgallery.py:13515 ../gtkrawgallery.py:13696 +#: ../gtkrawgallery.py:13738 ../gtkrawgallery.py:14395 +#: ../gtkrawgallery.py:14710 +#, python-format +msgid "There are %(n)s images Total size %(mb).3f MB" +msgstr "Ci sono %(n)s immagini Spazio occupato %(mb).3f MB" + +#: ../gtkrawgallery.py:10466 ../gtkrawgallery.py:10579 +#: ../gtkrawgallery.py:10752 ../gtkrawgallery.py:10840 +#: ../gtkrawgallery.py:10889 ../gtkrawgallery.py:12854 +#: ../gtkrawgallery.py:13150 ../gtkrawgallery.py:13383 +#: ../gtkrawgallery.py:13521 ../gtkrawgallery.py:13700 +#: ../gtkrawgallery.py:14215 +msgid "There are 0 images" +msgstr "Ci sono 0 immagini" + +#: ../gtkrawgallery.py:10611 +#, python-format +msgid "There are %(n)s images Total size %(mb).3f MB " +msgstr "Ci sono %(n)s immagini Spazio occupato %(mb).3f MB" + +#: ../gtkrawgallery.py:10614 +#, python-format +msgid "There are %s images" +msgstr "Ci sono %s immagini" + +#: ../gtkrawgallery.py:10657 +#, python-format +msgid "" +"Attention! The %s archive has been modified!\n" +"The album will be updated!" +msgstr "" +"Attenzione! L' archivio %s è stato modificato!\n" +"L'album verrà aggiornato!" + +#: ../gtkrawgallery.py:10658 ../gtkrawgallery.py:12828 +#: ../gtkrawgallery.py:13049 +msgid "Warning!" +msgstr "Attenzione!" + +#: ../gtkrawgallery.py:10765 gtkrg.ui.h:249 +msgid "Open Album" +msgstr "Apri Album" + +#: ../gtkrawgallery.py:10766 +msgid "Choose the album to open" +msgstr "Seleziona l'album da aprire" + +#: ../gtkrawgallery.py:10787 gtkrg.ui.h:168 publisher.ui.h:10 +msgid "Insert the new album name" +msgstr "Inserisci un nuovo nome per l'album" + +#: ../gtkrawgallery.py:10788 gtkrg.ui.h:209 publisher.ui.h:13 +msgid "New Album" +msgstr "Nuovo Album" + +#: ../gtkrawgallery.py:10806 +msgid "" +"This album name already exists.\n" +"Choose another name!" +msgstr "" +"Questo album esiste già.\n" +"Scegli un altro nome!" + +#: ../gtkrawgallery.py:10829 ../gtkrawgallery.py:10873 gtkrg.ui.h:94 +msgid "Delete Album" +msgstr "Elimina Album" + +#: ../gtkrawgallery.py:10874 +msgid "Choose the album to delete!" +msgstr "Seleziona l'album da eliminare!" + +#: ../gtkrawgallery.py:10924 +#, python-format +msgid "" +"Insert a new name for\n" +" %s" +msgstr "" +"Inserisci un nuovo nome per\n" +"%s" + +#: ../gtkrawgallery.py:12827 +#, python-format +msgid "" +"Attention! Unknown file: %s\n" +" The gallery will be updated!" +msgstr "" +"Attenzione! File sconosciuto: %s\n" +"La galleria verrà aggiornata!" + +#: ../gtkrawgallery.py:12999 +msgid "Copying..." +msgstr "Copia in corso..." + +#: ../gtkrawgallery.py:13000 +msgid "Choose a destination directory where to copy" +msgstr "Scegli la cartella di destinazione dove copiare" + +#: ../gtkrawgallery.py:13004 +msgid "Moving..." +msgstr "Spostamento in corso..." + +#: ../gtkrawgallery.py:13005 +msgid "Choose a destination directory where to move" +msgstr "Scegli la cartella di destinazione dove spostare" + +#: ../gtkrawgallery.py:13047 +msgid "Moving album images is not permitted!" +msgstr "Non è permesso spostare le immagini di un album!" + +#: ../gtkrawgallery.py:13072 ../gtkrawgallery.py:13079 +#: ../gtkrawgallery.py:13086 +msgid "" +"Operation terminated with errors!\n" +"Open the Error Log for details." +msgstr "" +"Operazione terminata con errori!\n" +"Apri il registro degli errori per maggiori dettagli." + +#: ../gtkrawgallery.py:13406 +msgid "Are you sure to delete selected images?" +msgstr "Vuoi eliminare le immagini selezionate?" + +#: ../gtkrawgallery.py:13442 +msgid "Deleting..." +msgstr "Eliminazione in corso..." + +#: ../gtkrawgallery.py:13535 ../gtkrawgallery.py:13611 +#, python-format +msgid "Delete %s ?" +msgstr "Eliminare %s ?" + +#: ../gtkrawgallery.py:13741 +#, python-format +msgid "" +"A file named \"%s\" \n" +"already exists! Overwrite it?" +msgstr "" +"Un file di nome \"%s\"\n" +"esiste già! Vuoi sovrascriverlo?" + +#: ../gtkrawgallery.py:13929 +msgid "Calculating timestamp..." +msgstr "Calcolo data creazione..." + +#: ../gtkrawgallery.py:13958 ../gtkrawgallery.py:14080 gtkrg.ui.h:314 +msgid "Scan" +msgstr "Scan" + +#: ../gtkrawgallery.py:14048 +msgid "Scanning..." +msgstr "Scansione..." + +#: ../gtkrawgallery.py:14049 +msgid "Stop" +msgstr "Stop" + +#: ../gtkrawgallery.py:14074 +#, python-format +msgid "Found: %s" +msgstr "Trovati: %s" + +#: ../gtkrawgallery.py:14078 gtkrg.ui.h:381 +msgid "" +"This tool will try to find tagged images\n" +"into the filesystem updating the tags database." +msgstr "" +"Questo strumento serve a sincronizzare il database delle etichette\n" +"con le immagini contenenti etichette nei metadati." + +#: ../gtkrawgallery.py:14212 +#, python-format +msgid "There are %(n)d images Total size %(mb).3f MB" +msgstr "Ci sono %(n)d immagini Spazio occupato %(mb).3f MB" + +#: ../gtkrawgallery.py:14956 +msgid "" +"Are you sure to delete\n" +"all cached thumbnails?" +msgstr "" +"Vuoi eliminare\n" +"tutte le anteprime nella cache?" + +#: ../gtkrawgallery.py:15045 +msgid "Loading histogram..." +msgstr "Caricamento istogramma..." + +#: ../gtkrawgallery.py:15315 +msgid "saving data..." +msgstr "salvataggio impostazioni..." + +#: gtkrg_dropbox.py:42 +msgid "Upload into DropBox" +msgstr "Carica su DropBox" + +#: gtkrg_dropbox.py:57 +msgid "Dropbox user:" +msgstr "Dropbox user:" + +#: gtkrg_dropbox.py:58 +msgid "New folder" +msgstr "Nuova cartella" + +#: gtkrg_dropbox.py:59 +msgid "creates a new destination folder" +msgstr "Crea una cartella di destinazione" + +#: gtkrg_dropbox.py:60 +msgid "insert the folder name" +msgstr "Inserisci il nome della cartella" + +#: gtkrg_dropbox.py:62 +msgid "Upload to:" +msgstr "Carica su:" + +#: gtkrg_dropbox.py:77 +msgid "Log into Dropbox" +msgstr "Accesso a DropBox" + +#: gtkrg_dropbox.py:108 gtkrg_facebook.py:99 gtkrg_flickr.py:95 +msgid "
Page Loading Error!
" +msgstr "
Errore nel caricamento della pagina!
" + +#: gtkrg_dropbox.py:211 +msgid "not logged!" +msgstr "errore accesso!" + +#: gtkrg_dropbox.py:224 gtkrg_facebook.py:263 gtkrg_flickr.py:375 +#: gtkrg_picasaweb.py:287 +#, python-format +msgid "Uploading %s ..." +msgstr "Invio in corso di %s..." + +#: gtkrg_dropbox.py:287 gtkrg_facebook.py:391 gtkrg_facebook.py:488 +#: gtkrg_flickr.py:487 +msgid "Continue" +msgstr "Continua" + +#: gtkrg_dropbox.py:290 +msgid "" +"
Log into Dropbox to authorize GTKRawGallery and click OK.

Click " +"Continue.

" +msgstr "" +"
Effettua l'accesso a Dropbox per autorizzare GTKRawGallery e premi OK." +"

Fai click su Continua.

" + +#: gtkrg_dropbox.py:302 gtkrg_dropbox.py:306 gtkrg_dropbox.py:361 +#: gtkrg_flickr.py:517 gtkrg_flickr.py:525 gtkrg_flickr.py:561 +#: gtkrg_picasaweb.py:418 +msgid "Log-in failed!" +msgstr "Accesso fallito!" + +#: gtkrg_dropbox.py:319 +msgid "authenticating..." +msgstr "autenticazione in corso..." + +#: gtkrg_dropbox.py:334 +msgid "Retrieving directories..." +msgstr "Recupero cartelle..." + +#: gtkrg_dropbox.py:344 +#, python-format +msgid "Upload into the DropBox of %s" +msgstr "Carica sul DropBox di %s" + +#: gtkrg_dropbox.py:345 +msgid "You are logged!" +msgstr "accesso avvenuto!" + +#: gtkrg_facebook.py:75 +msgid "Log into Facebook" +msgstr "Accesso a Facebook" + +#: gtkrg_facebook.py:408 gtkrg_flickr.py:458 gtkrg_picasaweb.py:348 +msgid "Retrieving albums..." +msgstr "Recupero album..." + +#: gtkrg_facebook.py:419 +msgid "Token expired!" +msgstr "Token scaduto!" + +#: gtkrg_facebook.py:428 +#, python-format +msgid "Publish on %s Facebook account" +msgstr "Pubblica sull' account Facebook di %s" + +#: gtkrg_facebook.py:452 gtkrg_flickr.py:473 gtkrg_picasaweb.py:358 +#, python-format +msgid "you have %s albums" +msgstr "hai %s album" + +#: gtkrg_facebook.py:469 +msgid "" +"
Log into your Facebook to authorize GTKRawGallery and click OK.

Click " +"Continue.

" +msgstr "" +"
Effettua l'accesso a Facebook per autorizzare GTKRawGallery e premi OK." +"

Fai click su Continua.

" + +#: gtkrg_facebook.py:484 +msgid "Publish on Facebook" +msgstr "Pubblica su Facebook" + +#: gtkrg_flickr.py:47 +msgid "Publish on Flickr" +msgstr "Pubblica su Flickr" + +#: gtkrg_flickr.py:58 +msgid "Flickr user:" +msgstr "Flickr user:" + +#: gtkrg_flickr.py:68 +msgid "Log into Flickr" +msgstr "Accesso a Flickr" + +#: gtkrg_flickr.py:325 +msgid "connection error!" +msgstr "errore connessione!" + +#: gtkrg_flickr.py:476 gtkrg_flickr.py:480 gtkrg_smtp.py:86 +msgid "Login failed!" +msgstr "Accesso fallito!" + +#: gtkrg_flickr.py:488 +msgid "" +"
Log into Flickr to authorize GTKRawGallery and click OK.

Click on " +"continue.

" +msgstr "" +"
Effettua l'accesso a Flickr per autorizzare GTKRawGallery e premi OK." +"

Fai click su Continua.

" + +#: gtkrg_flickr.py:510 gtkrg_flickr.py:541 +#, python-format +msgid "Publish on %s Flickr account" +msgstr "Pubblica sull'account Flickr di %s" + +#: gtkrg_picasaweb.py:48 +msgid "Publish on Picasa Web Albums" +msgstr "Pubblica su Picasa Web Albums" + +#: gtkrg_picasaweb.py:49 +msgid "Log into Picasa Web Albums" +msgstr "Accedi a Picasa Web Albums" + +#: gtkrg_picasaweb.py:386 +msgid "Authenticating..." +msgstr "Autenticazione..." + +#: gtkrg_picasaweb.py:412 +msgid "Invalid username or password!" +msgstr "username o password errati!" + +#: gtkrg_print.py:222 +#, python-format +msgid "%(w).1f x %(h).1f millimeters" +msgstr "%(w).1f x %(h).1f millimetri" + +#: gtkrg_print.py:334 +#, python-format +msgid "%(w).3f x %(h).3f %(text)s" +msgstr "%(w).3f x %(h).3f %(text)s" + +#: gtkrg_smtp.py:81 +msgid "Connection failed!" +msgstr "Connessione fallita!" + +#: gtkrg_smtp.py:93 +msgid "Disconnection failed!" +msgstr "Disconnessione fallita!" + +#: gtkrg_smtp.py:133 +msgid "Email has not been sent" +msgstr "Email non inviata" + +#: gtkrg.ui.h:1 +msgid "16 bit" +msgstr "16 bit" + +#: gtkrg.ui.h:2 +msgid "16 bit linear" +msgstr "16 bit lineari" + +#: gtkrg.ui.h:3 +msgid "25" +msgstr "25" + +#: gtkrg.ui.h:4 +msgid "8 bit" +msgstr "8 bit" + +#: gtkrg.ui.h:5 +msgid ":" +msgstr ":" + +#: gtkrg.ui.h:6 +msgid "" +"Zoom in and right click on the red area!\n" +"Use the mouse wheel to adjust the circle size." +msgstr "" +"Selezionare la zona rossa e fare clic col tasto destro del mouse!\n" +"Usare la rotellina del mouse per aggiustare il cerchio." + +#: gtkrg.ui.h:8 +msgid "About Tag Manager" +msgstr "Informazioni sul gestore delle etichette" + +#: gtkrg.ui.h:9 +msgid "Account Settings" +msgstr "Impostazioni account" + +#: gtkrg.ui.h:10 +msgid "Add Border" +msgstr "Aggiungi bordo" + +#: gtkrg.ui.h:11 +msgid "Add Fading Transition" +msgstr "Aggiungi transizione" + +#: gtkrg.ui.h:12 +msgid "Add Noise" +msgstr "Aggiungi rumore" + +#: gtkrg.ui.h:13 +msgid "Add To Batch Queue" +msgstr "Aggiungi alla coda del processore batch" + +#: gtkrg.ui.h:14 +msgid "Add and click row to edit" +msgstr "Aggiungi e fai clic sulla riga per editare" + +#: gtkrg.ui.h:15 +msgid "Add new keyword to list" +msgstr "Aggiungi nuova etichetta alla lista" + +#: gtkrg.ui.h:16 +msgid "Add new tag to list" +msgstr "Aggiungi nuova etichetta alla lista" + +#: gtkrg.ui.h:17 +msgid "Add tag to list" +msgstr "Aggiungi etichetta alla lista" + +#: gtkrg.ui.h:18 +msgid "Add to Album" +msgstr "Aggiungi all'album" + +#: gtkrg.ui.h:19 +msgid "Add to album" +msgstr "Aggiungi all'album" + +#: gtkrg.ui.h:20 +msgid "Add to list" +msgstr "Aggiungi alla lista" + +#: gtkrg.ui.h:21 +msgid "Advanced Metadata Editor" +msgstr "Editor dei metadati avanzato" + +#: gtkrg.ui.h:22 +msgid "Advanced controls" +msgstr "Controlli avanzati" + +#: gtkrg.ui.h:23 +msgid "Album" +msgstr "Album" + +#: gtkrg.ui.h:25 +msgid "Appearance" +msgstr "Aspetto" + +#: gtkrg.ui.h:26 +msgid "Apply Tags" +msgstr "Applica etichette" + +#: gtkrg.ui.h:27 +msgid "Apply camera ICC profile" +msgstr "Applica profilo ICC della fotocamera" + +#: gtkrg.ui.h:28 +msgid "Apply output ICC profile" +msgstr "Applica profilo ICC in output" + +#: gtkrg.ui.h:29 +msgid "Apply style" +msgstr "Applica stile" + +#: gtkrg.ui.h:30 +msgid "Apply tags" +msgstr "Applica etichette" + +#: gtkrg.ui.h:31 +msgid "Apply tags to selected images" +msgstr "Applica etichette alla selezione" + +#: gtkrg.ui.h:32 +msgid "Attention" +msgstr "Attenzione" + +#: gtkrg.ui.h:34 +msgid "" +"Attention, this image has an embedded ICC profile!\n" +"What do you want to do with it?" +msgstr "" +"Attenzione, questa immagine contiene un profilo ICC!\n" +"Cosa vuoi fare?" + +#: gtkrg.ui.h:36 +msgid "Back" +msgstr "immagine precedente" + +#: gtkrg.ui.h:37 +msgid "Background Music" +msgstr "Sottofondo musicale" + +#: gtkrg.ui.h:38 +msgid "Barrel" +msgstr "Barile" + +#: gtkrg.ui.h:39 +msgid "Batch Manager" +msgstr "Gestore Coda Batch" + +#: gtkrg.ui.h:40 +msgid "Before" +msgstr "Prima" + +#: gtkrg.ui.h:42 +msgid "Black point" +msgstr "Punto nero" + +#: gtkrg.ui.h:43 +msgid "Blank disk first to write" +msgstr "Cancella il disco prima di scriverlo" + +#: gtkrg.ui.h:44 +msgid "Blue" +msgstr "Blu" + +#: gtkrg.ui.h:47 +msgid "Brightness:" +msgstr "Luminosità:" + +#: gtkrg.ui.h:48 +msgid "Browser" +msgstr "Browser" + +#: gtkrg.ui.h:49 +msgid "Burn" +msgstr "Masterizza" + +#: gtkrg.ui.h:50 +msgid "Burn disk" +msgstr "Scrivi disco" + +#: gtkrg.ui.h:51 +msgid "Cache Thumbnails" +msgstr "Cache anteprime" + +#: gtkrg.ui.h:52 +msgid "Cache level:" +msgstr "Livello cache:" + +#: gtkrg.ui.h:53 publisher.ui.h:5 +msgid "Cancel" +msgstr "Cancella" + +#: gtkrg.ui.h:54 +msgid "Channel Mixer" +msgstr "Mixer canali" + +#: gtkrg.ui.h:55 +msgid "Channel multipliers" +msgstr "Moltiplicatori canale" + +#: gtkrg.ui.h:56 +msgid "Channel:" +msgstr "Canale:" + +#: gtkrg.ui.h:58 +msgid "Check a category to tag image and vice versa" +msgstr "Seleziona una categoria per etichettare l'immagine e viceversa" + +#: gtkrg.ui.h:59 +msgid "Choose Presets to delete" +msgstr "Seleziona preset da eliminare" + +#: gtkrg.ui.h:60 +msgid "Choose a destination directory" +msgstr "Seleziona cartella di destinazione" + +#: gtkrg.ui.h:61 +msgid "Choose a gallery layout:" +msgstr "Seleziona il formato della galleria:" + +#: gtkrg.ui.h:62 +msgid "Choose album directory" +msgstr "Scegli la cartella dell'album" + +#: gtkrg.ui.h:63 +msgid "Choose destination album" +msgstr "Scegli album di destinazione" + +#: gtkrg.ui.h:64 +msgid "Choose the album name to delete" +msgstr "Scegli l'album da eliminare" + +#: gtkrg.ui.h:65 +msgid "Choose the template to delete" +msgstr "Scegli il template da eliminare" + +#: gtkrg.ui.h:66 +msgid "Clear Cache" +msgstr "Cancella Cache" + +#: gtkrg.ui.h:67 +msgid "Clear Keywords" +msgstr "Cancella etichette" + +#: gtkrg.ui.h:68 +msgid "Clear Tag database" +msgstr "Cancella database delle etichette" + +#: gtkrg.ui.h:69 +msgid "Clear all tags" +msgstr "Cancella tutte le etichette" + +#: gtkrg.ui.h:70 +msgid "Clear list when done!" +msgstr "Cancella lista appena finito!" + +#: gtkrg.ui.h:71 +msgid "Clear tag list" +msgstr "Cancella lista delle etichette" + +#: gtkrg.ui.h:72 +msgid "Close Development Window" +msgstr "Chiudi finestra di sviluppo" + +#: gtkrg.ui.h:73 +msgid "Color" +msgstr "Colore" + +#: gtkrg.ui.h:74 +msgid "Color Balance" +msgstr "Bilanciamento colore" + +#: gtkrg.ui.h:75 +msgid "Color Depth" +msgstr "Profondità colore" + +#: gtkrg.ui.h:76 +msgid "Color Management" +msgstr "Gestione Colore" + +#: gtkrg.ui.h:77 +msgid "Color Manager" +msgstr "Gestore Profili Colore" + +#: gtkrg.ui.h:78 +msgid "Command:" +msgstr "Comando:" + +#: gtkrg.ui.h:80 +msgid "Copy to" +msgstr "Copia in" + +#: gtkrg.ui.h:81 +msgid "Correct chromatic aberration" +msgstr "Correggi aberrazione cromatica" + +#: gtkrg.ui.h:83 +msgid "Curves" +msgstr "Curve" + +#: gtkrg.ui.h:84 +msgid "Cyan" +msgstr "Ciano" + +#: gtkrg.ui.h:85 +msgid "Dark theme" +msgstr "Tema Dark" + +#: gtkrg.ui.h:86 +msgid "Dcraw" +msgstr "Dcraw" + +#: gtkrg.ui.h:87 +msgid "Dcraw supported formats" +msgstr "Formati supportati da Dcraw" + +#: gtkrg.ui.h:88 +msgid "Debug" +msgstr "Debug" + +#: gtkrg.ui.h:89 +msgid "Debugging output" +msgstr "Output di debug" + +#: gtkrg.ui.h:90 +msgid "Default Settings" +msgstr "Impostazioni predefinite" + +#: gtkrg.ui.h:91 +msgid "Default theme" +msgstr "Tema predefinito" + +#: gtkrg.ui.h:92 +msgid "Degree" +msgstr "Gradi" + +#: gtkrg.ui.h:93 +msgid "Delay:" +msgstr "Ritardo:" + +#: gtkrg.ui.h:95 +msgid "Delete Image" +msgstr "Elimina Immagine" + +#: gtkrg.ui.h:96 +msgid "Delete Presets" +msgstr "Elimina i preset" + +#: gtkrg.ui.h:97 +msgid "Delete Template" +msgstr "Elimina Template" + +#: gtkrg.ui.h:98 +msgid "Delete current preset" +msgstr "Elimina preset corrente" + +#: gtkrg.ui.h:99 +msgid "Denoise:" +msgstr "Riduzione rumore:" + +#: gtkrg.ui.h:100 +msgid "Destination" +msgstr "Destinazione" + +#: gtkrg.ui.h:101 +msgid "Destination:" +msgstr "Destinazione:" + +#: gtkrg.ui.h:102 +msgid "Details" +msgstr "Dettagli" + +#: gtkrg.ui.h:103 +msgid "Device:" +msgstr "Dispositivo:" + +#: gtkrg.ui.h:104 +msgid "Display ICC Profile:" +msgstr "Profilo ICC del Display" + +#: gtkrg.ui.h:105 +msgid "Display Rendering Intent" +msgstr "Display Rendering Intent" + +#: gtkrg.ui.h:106 +msgid "Display profile Info!" +msgstr "Informazioni sul profilo del display" + +#: gtkrg.ui.h:107 +msgid "Do not ask me again" +msgstr "Non chiedermelo più" + +#: gtkrg.ui.h:108 +msgid "Do you want to save last changes?" +msgstr "Salvare le ultime modifiche?" + +#: gtkrg.ui.h:109 +msgid "Document mode (totally raw)" +msgstr "Modalità documento (totalmente raw)" + +#: gtkrg.ui.h:110 +msgid "Don't alert me again" +msgstr "Non avvisarmi più" + +#: gtkrg.ui.h:111 +msgid "Don't alert me again!" +msgstr "Non avvisarmi più" + +#: gtkrg.ui.h:112 +msgid "Don't ask me again" +msgstr "Non chiedermelo più" + +#: gtkrg.ui.h:113 +msgid "Don't stretch or rotate raw pixels" +msgstr "Non stirare o ruotare i pixel" + +#: gtkrg.ui.h:114 +msgid "Edit WB Presets" +msgstr "Edita preset WB" + +#: gtkrg.ui.h:115 +msgid "Effects" +msgstr "Effetti" + +#: gtkrg.ui.h:116 +msgid "Enable Color Management" +msgstr "Abilita gestione colore" + +#: gtkrg.ui.h:117 +msgid "Enhance" +msgstr "Migliora" + +#: gtkrg.ui.h:118 +msgid "Error Log" +msgstr "Registro Errori" + +#: gtkrg.ui.h:119 +msgid "Exif" +msgstr "Exif" + +#: gtkrg.ui.h:120 +msgid "Exiftool warning!" +msgstr "Exiftool warning!" + +#: gtkrg.ui.h:121 +msgid "Exposure" +msgstr "Esposizione" + +#: gtkrg.ui.h:122 +msgid "Exposure, saturation, hue" +msgstr "Esposizione, saturazione, tonalità" + +#: gtkrg.ui.h:123 +msgid "Extension" +msgstr "Estensione" + +#: gtkrg.ui.h:124 +msgid "Extension:" +msgstr "Estensione:" + +#: gtkrg.ui.h:125 +msgid "Facebook" +msgstr "Facebook" + +#: gtkrg.ui.h:126 +msgid "Fast loading ( half size image)" +msgstr "Caricamento rapido (dimensioni dimezzate)" + +#: gtkrg.ui.h:128 +msgid "File Open Behaviour:" +msgstr "Comportamento apertura file:" + +#: gtkrg.ui.h:129 +msgid "Filter" +msgstr "Filtro" + +#: gtkrg.ui.h:130 +msgid "Fine" +msgstr "Fine" + +#: gtkrg.ui.h:131 +msgid "Fine Rotation" +msgstr "Rotazione fine" + +#: gtkrg.ui.h:132 +msgid "First" +msgstr "Prima immagine" + +#: gtkrg.ui.h:133 +msgid "Flickr" +msgstr "Flickr" + +#: gtkrg.ui.h:134 +msgid "Flip Horizontally" +msgstr "Ribalta orizzontalmente" + +#: gtkrg.ui.h:135 +msgid "Flip Vertically" +msgstr "Ribalta verticalmente" + +#: gtkrg.ui.h:136 +msgid "Formats" +msgstr "Formati" + +#: gtkrg.ui.h:137 +msgid "Forward" +msgstr "Immagine successiva" + +#: gtkrg.ui.h:138 +msgid "From:" +msgstr "Da:" + +#: gtkrg.ui.h:139 +msgid "Full image workflow (slow!)" +msgstr "Applica il workflow sull'immagine intera (lento)" + +#: gtkrg.ui.h:140 +msgid "Full metadata list" +msgstr "Lista metadati completa" + +#: gtkrg.ui.h:141 +msgid "Fullscreen Gallery" +msgstr "Galleria a schermo intero" + +#: gtkrg.ui.h:142 +msgid "Fullscreen Image" +msgstr "Immagine a schermo intero" + +#: gtkrg.ui.h:143 +msgid "" +"Gamma/\n" +"mid-tones" +msgstr "" +"Gamma/\n" +"toni medi" + +#: gtkrg.ui.h:145 +msgid "Gamma:" +msgstr "Gamma:" + +#: gtkrg.ui.h:146 +msgid "Gimp" +msgstr "Gimp" + +#: gtkrg.ui.h:147 +msgid "Gradient:" +msgstr "Gradiente:" + +#: gtkrg.ui.h:148 +msgid "Gray-Scale" +msgstr "Scala di grigi" + +#: gtkrg.ui.h:149 +msgid "Green" +msgstr "Verde" + +#: gtkrg.ui.h:150 +msgid "Group:" +msgstr "Gruppo:" + +#: gtkrg.ui.h:151 +msgid "Gtk supported formats" +msgstr "Formati supportati da Gtk" + +#: gtkrg.ui.h:152 +msgid "Highlight mode:" +msgstr "Modalità alte luci:" + +#: gtkrg.ui.h:153 +msgid "Highlights" +msgstr "Alte luci" + +#: gtkrg.ui.h:154 +msgid "History" +msgstr "Cronologia" + +#: gtkrg.ui.h:155 +msgid "Horizontal" +msgstr "Orizzontale" + +#: gtkrg.ui.h:156 +msgid "Horizontal split" +msgstr "Split orizzontale" + +#: gtkrg.ui.h:157 +msgid "Hue" +msgstr "Tonalità" + +#: gtkrg.ui.h:158 +msgid "Ignore" +msgstr "ignora" + +#: gtkrg.ui.h:160 +msgid "Input ICC Profile:" +msgstr "Profilo ICC di input:" + +#: gtkrg.ui.h:161 +msgid "Input profile info!" +msgstr "Informazioni sul profilo di input" + +#: gtkrg.ui.h:163 +msgid "" +"Insert a new name or a pattern to \n" +"rename multiple files progressively.\n" +"(e.g. IMG01.CR2, IMG02.CR2, IMG03.CR2,...,\n" +"or IMG_01.CR2, IMG_02.CR2, IMG_03.CR2,...,)" +msgstr "" +"Inserire un nome o un pattern\n" +"per rinominare una selezione multipla.\n" +"(esempio IMG01.CR2, IMG02.CR2, IMG03.CR2,...,\n" +"o IMG_01.CR2, IMG_02.CR2, IMG_03.CR2,...,)" + +#: gtkrg.ui.h:167 +msgid "Insert here the file extension you know to be supported by dcraw." +msgstr "Inserire un nuovo formato se supportato da dcraw." + +#: gtkrg.ui.h:169 +msgid "Interpolate RGGB as four colors" +msgstr "Interpolazione RGGB a quattro colori" + +#: gtkrg.ui.h:170 +msgid "Interpolation quality:" +msgstr "Qualità interpolazione:" + +#: gtkrg.ui.h:171 +msgid "Inverted Selection" +msgstr "Selezione inversa" + +#: gtkrg.ui.h:172 +msgid "Iptc" +msgstr "Iptc" + +#: gtkrg.ui.h:173 +msgid "Keep" +msgstr "Tieni" + +#: gtkrg.ui.h:174 +msgid "Kelvin" +msgstr "Kelvin" + +#: gtkrg.ui.h:175 +msgid "Keyword:" +msgstr "Etichetta:" + +#: gtkrg.ui.h:177 +msgid "Last" +msgstr "Ultima immagine" + +#: gtkrg.ui.h:178 +msgid "Layout" +msgstr "Formato" + +#: gtkrg.ui.h:179 +msgid "Layout 1" +msgstr "Formato 1" + +#: gtkrg.ui.h:180 +msgid "Layout 2" +msgstr "Formato 2" + +#: gtkrg.ui.h:181 +msgid "Layout 3" +msgstr "Formato 3" + +#: gtkrg.ui.h:182 +msgid "Layout 4" +msgstr "Formato 4" + +#: gtkrg.ui.h:183 +msgid "Lens Distortion" +msgstr "Distorsione lente" + +#: gtkrg.ui.h:184 +msgid "Levels, gamma" +msgstr "Livelli, gamma" + +#: gtkrg.ui.h:185 +msgid "Linear" +msgstr "Lineare" + +#: gtkrg.ui.h:186 +msgid "Load Presets" +msgstr "Carica preset" + +#: gtkrg.ui.h:187 +msgid "Logarithmic" +msgstr "Logaritmica" + +#: gtkrg.ui.h:188 +msgid "MByte" +msgstr "MByte" + +#: gtkrg.ui.h:189 +msgid "Magenta" +msgstr "Magenta" + +#: gtkrg.ui.h:190 +msgid "Mail" +msgstr "Posta" + +#: gtkrg.ui.h:191 +msgid "Mail Client" +msgstr "Client di posta" + +#: gtkrg.ui.h:192 +msgid "Makernotes" +msgstr "Makernotes" + +#: gtkrg.ui.h:193 +msgid "Manual tagging" +msgstr "Etichettatura manuale" + +#: gtkrg.ui.h:194 +msgid "Manual white balance" +msgstr "Bilanciamento del bianco manuale" + +#: gtkrg.ui.h:195 +msgid "Max cache Size:" +msgstr "Dimensione massima della cache:" + +#: gtkrg.ui.h:196 +msgid "Median Filter" +msgstr "Filtro mediano" + +#: gtkrg.ui.h:197 +msgid "Median filter:" +msgstr "Filtro mediano:" + +#: gtkrg.ui.h:198 +msgid "Message:" +msgstr "Messaggio:" + +#: gtkrg.ui.h:199 +msgid "Metadata" +msgstr "Metadata" + +#: gtkrg.ui.h:200 +msgid "Mid-point" +msgstr "Punto medio" + +#: gtkrg.ui.h:201 +msgid "Mid-tones contrast" +msgstr "Contrasto dei toni medi" + +#: gtkrg.ui.h:202 +msgid "Monochrome" +msgstr "Monocromatico" + +#: gtkrg.ui.h:203 +msgid "Move to" +msgstr "Sposta in" + +#: gtkrg.ui.h:204 +msgid "Multisession" +msgstr "Multisessione" + +#: gtkrg.ui.h:205 +msgid "Name:" +msgstr "Nome:" + +#: gtkrg.ui.h:206 +msgid "Needs restarting!" +msgstr "Richiede il riavvio del programma." + +#: gtkrg.ui.h:208 +msgid "New" +msgstr "Nuovo" + +#: gtkrg.ui.h:210 +msgid "New Keyword" +msgstr "Nuova Etichetta" + +#: gtkrg.ui.h:211 +msgid "New Tag" +msgstr "Nuova etichetta" + +#: gtkrg.ui.h:212 +msgid "New tag" +msgstr "Nuova etichetta" + +#: gtkrg.ui.h:213 +msgid "" +"Next Image\n" +"Previous Image\n" +"First Image\n" +"Last Image\n" +"Zoom In\n" +"Zoom Out\n" +"Zoom 1:1\n" +"Zoom Fit\n" +"Rotate Left\n" +"Rotate Right\n" +"Select All\n" +"Unselect\n" +"Mail\n" +"Burn CD/DVD\n" +"Invert Selection\n" +"Fullscreen\n" +"Slideshow\n" +"Open Folder\n" +"Open Album\n" +"Save As\n" +"Copy To\n" +"Move To\n" +"New Album\n" +"Print\n" +"Tag Manager\n" +"Add to Album\n" +"Layout 1\n" +"Layout 2\n" +"Layout 3\n" +"Layout 4\n" +"Fullscreen gallery mode\n" +"Quit\n" +"Delete\t\n" +"Close Development Window " +msgstr "" +"Prossima immagine\n" +"Immagine precedente\n" +"Prima immagine\n" +"Ultima immagine\n" +"Ingrandisci\n" +"Rimpicciolisci\n" +"Adatta alla finestra\n" +"Dimensione intera\n" +"Ruota a sinistra\n" +"Ruota a destra\n" +"Seleziona tutto\n" +"Annulla selezione\n" +"Posta\n" +"Scrivi CD/DVD\n" +"Selezione inversa\n" +"Schermo intero\n" +"Presentazione\n" +"Apri cartella\n" +"Apri album\n" +"Salva come\n" +"Copia\n" +"Muovi\n" +"Nuovo album\n" +"Stampa\n" +"Gestore etichette\n" +"Aggiungi all'album\n" +"Formato 1\n" +"Formato 2\n" +"Formato 3\n" +"Formato 4\n" +"Galleria a schermo intero\n" +"Esci\n" +"Elimina\n" +"Chiudi finestra di sviluppo" + +#: gtkrg.ui.h:248 +msgid "Oil Paint" +msgstr "Pittura a olio" + +#: gtkrg.ui.h:250 +msgid "Open Folder" +msgstr "Apri cartella" + +#: gtkrg.ui.h:251 +msgid "Open album" +msgstr "Apri album" + +#: gtkrg.ui.h:252 +msgid "Open on development window" +msgstr "Apri nella finestra di sviluppo" + +#: gtkrg.ui.h:253 +msgid "Open with..." +msgstr "Apri con..." + +#: gtkrg.ui.h:254 +msgid "Other..." +msgstr "Altro..." + +#: gtkrg.ui.h:255 +msgid "Output ICC Profile:" +msgstr "Profilo ICC di output:" + +#: gtkrg.ui.h:256 +msgid "Output Rendering Intent" +msgstr "Output Rendering Intent" + +#: gtkrg.ui.h:257 +msgid "Output colorspace:" +msgstr "Spazio colore di output:" + +#: gtkrg.ui.h:258 +msgid "Output profile Info!" +msgstr "Informazioni sul profilo di output" + +#: gtkrg.ui.h:259 +msgid "Overwrite all" +msgstr "Sovrascrivi tutto" + +#: gtkrg.ui.h:260 publisher.ui.h:15 +msgid "Password:" +msgstr "Password:" + +#: gtkrg.ui.h:261 +msgid "Picasa Web Albums" +msgstr "Picasa Web Albums" + +#: gtkrg.ui.h:262 +msgid "Pick black point" +msgstr "scegli punto nero" + +#: gtkrg.ui.h:263 +msgid "Pick gray point" +msgstr "scegli punto grigio" + +#: gtkrg.ui.h:264 +msgid "Pick white point" +msgstr "scegli punto bianco" + +#: gtkrg.ui.h:265 +msgid "Pincussion" +msgstr "Pincussion" + +#: gtkrg.ui.h:266 +msgid "Port:" +msgstr "Porta:" + +#: gtkrg.ui.h:268 +msgid "Presets" +msgstr "Preset" + +#: gtkrg.ui.h:269 +msgid "Press ESC to exit fullscreen" +msgstr "Premere ESC per uscire dalla modalità a schermo intero" + +#: gtkrg.ui.h:270 +msgid "Print" +msgstr "Stampa" + +#: gtkrg.ui.h:271 publisher.ui.h:17 +msgid "Publish" +msgstr "Pubblica" + +#: gtkrg.ui.h:272 +msgid "Publish to..." +msgstr "Pubblica in.." + +#: gtkrg.ui.h:273 +msgid "Quality" +msgstr "Qualità" + +#: gtkrg.ui.h:274 +msgid "Red" +msgstr "Rosso" + +#: gtkrg.ui.h:275 +msgid "Red Eye Removal" +msgstr "Rimozione occhi rossi" + +#: gtkrg.ui.h:276 +msgid "Redo Workflow" +msgstr "workflow avanti" + +#: gtkrg.ui.h:277 +msgid "Reduce Noise" +msgstr "Riduzione rumore" + +#: gtkrg.ui.h:278 +msgid "Reduce Speckle Noise" +msgstr "Riduci rumore a macchie" + +#: gtkrg.ui.h:279 +msgid "Remember last visited path" +msgstr "Ricorda ultima cartella visitata" + +#: gtkrg.ui.h:280 +msgid "Remove Keywords" +msgstr "Rimuovi Etichette" + +#: gtkrg.ui.h:281 +msgid "Remove all checked items?" +msgstr "Rimuovere tutti gli oggetti spuntati?" + +#: gtkrg.ui.h:282 +msgid "Remove all keywords from selected images" +msgstr "Rimuovi tutte le etichette dalle immagini selezionate" + +#: gtkrg.ui.h:283 +msgid "Remove checked items" +msgstr "Rimuovi gli elementi spuntati" + +#: gtkrg.ui.h:284 +msgid "Remove checked keywords from selected images" +msgstr "Rimuovere le etichette spuntate dalle immagini selezionate" + +#: gtkrg.ui.h:285 +msgid "Remove row" +msgstr "Rimuovere riga" + +#: gtkrg.ui.h:286 +msgid "Remove selected keyword from list" +msgstr "Rimuovi le etichette selezionate dalla lista" + +#: gtkrg.ui.h:287 +msgid "Remove selected row" +msgstr "Rimuovere la righa selezionata" + +#: gtkrg.ui.h:288 +msgid "Remove tag" +msgstr "Rimuovi etichetta" + +#: gtkrg.ui.h:289 +msgid "Rename" +msgstr "Rinomina" + +#: gtkrg.ui.h:290 +msgid "Rename Images" +msgstr "Rinomina immagini" + +#: gtkrg.ui.h:292 +msgid "Reset" +msgstr "Reset" + +#: gtkrg.ui.h:293 +msgid "Reset All Channels" +msgstr "Ripristina tutti i canali" + +#: gtkrg.ui.h:294 +msgid "Reset Workflow" +msgstr "Reset Workflow" + +#: gtkrg.ui.h:295 +msgid "Reset all channels" +msgstr "Ripristina tutti i canali" + +#: gtkrg.ui.h:296 +msgid "Reset channel" +msgstr "Ripristina canale" + +#: gtkrg.ui.h:297 +msgid "Reset channels" +msgstr "Ripristina canali" + +#: gtkrg.ui.h:298 +msgid "Reset workflow?" +msgstr "Annullare il workflow?" + +#: gtkrg.ui.h:299 +msgid "Root directory" +msgstr "Cartella radice" + +#: gtkrg.ui.h:300 +msgid "Rotate Left" +msgstr "Ruota a sinistra" + +#: gtkrg.ui.h:301 +msgid "Rotate Right" +msgstr "Ruota a destra" + +#: gtkrg.ui.h:302 +msgid "Rotate image:" +msgstr "Ruota immagine:" + +#: gtkrg.ui.h:303 +msgid "Rounded corners" +msgstr "Angoli arrotondati" + +#: gtkrg.ui.h:304 +msgid "Saturation" +msgstr "Saturazione" + +#: gtkrg.ui.h:305 +msgid "Save As" +msgstr "Salva come" + +#: gtkrg.ui.h:306 +msgid "Save In:" +msgstr "Salva in:" + +#: gtkrg.ui.h:307 +msgid "Save Presets" +msgstr "Salva preset" + +#: gtkrg.ui.h:308 +msgid "Save Template" +msgstr "Salva Template" + +#: gtkrg.ui.h:309 +msgid "Save curve" +msgstr "Salva curva" + +#: gtkrg.ui.h:310 +msgid "Save curve preset" +msgstr "Salva preset curva" + +#: gtkrg.ui.h:311 +msgid "Save in:" +msgstr "Salva in:" + +#: gtkrg.ui.h:315 +msgid "Search" +msgstr "Cerca" + +#: gtkrg.ui.h:316 +msgid "Select A Folder" +msgstr "Seleziona una cartella" + +#: gtkrg.ui.h:317 +msgid "Select All" +msgstr "Seleziona tutto" + +#: gtkrg.ui.h:318 +msgid "Select Profile" +msgstr "Seleziona profilo" + +#: gtkrg.ui.h:319 +msgid "Selection Color" +msgstr "Colore selezione" + +#: gtkrg.ui.h:320 +msgid "Seleziona una cartella" +msgstr "Seleziona una cartella" + +#: gtkrg.ui.h:321 +msgid "Send" +msgstr "Invia" + +#: gtkrg.ui.h:322 +msgid "Sepia Tone" +msgstr "Tono seppia" + +#: gtkrg.ui.h:323 +msgid "Server:" +msgstr "Server:" + +#: gtkrg.ui.h:324 +msgid "Set as album preview" +msgstr "Imposta come miniatura dell'album" + +#: gtkrg.ui.h:325 +msgid "Set pause in seconds" +msgstr "Imposta pausa in secondi" + +#: gtkrg.ui.h:326 +msgid "Set white balance:" +msgstr "Imposta bilanciamento del bianco:" + +#: gtkrg.ui.h:327 +msgid "Shadows" +msgstr "Ombre" + +#: gtkrg.ui.h:329 +msgid "Shortcuts" +msgstr "Scorciatoie" + +#: gtkrg.ui.h:330 +msgid "Show a tooltip with image info when the pointer is over the thumbnail" +msgstr "" +"Mostra le informazioni dell'immagine quando il puntatore del mouse è sopra " +"l'anteprima" + +#: gtkrg.ui.h:331 +msgid "Show debugging output" +msgstr "Mostra l'output di debug" + +#: gtkrg.ui.h:332 +msgid "Show grid" +msgstr "Mostra griglia" + +#: gtkrg.ui.h:333 +msgid "Show hidden folders" +msgstr "Mostra cartelle invisibili" + +#: gtkrg.ui.h:334 +msgid "Show over exposed pixels" +msgstr "Mostra pixel sovraesposti" + +#: gtkrg.ui.h:335 +msgid "Show under exposed pixels" +msgstr "Mostra pixel sottoesposti" + +#: gtkrg.ui.h:336 +msgid "Simulates output device on your display" +msgstr "Simulare dispositivo di output nel display" + +#: gtkrg.ui.h:338 +msgid "Slideshow" +msgstr "Presentazione" + +#: gtkrg.ui.h:339 +msgid "Soft proofing" +msgstr "Soft proofing" + +#: gtkrg.ui.h:340 +msgid "Sort by" +msgstr "Ordina per" + +#: gtkrg.ui.h:342 +msgid "Square thumbnails" +msgstr "Anteprime quadrate" + +#: gtkrg.ui.h:343 +msgid "Start batch processor" +msgstr "Avvia processore batch" + +#: gtkrg.ui.h:344 +msgid "Strip Profiles" +msgstr "Elimina profili" + +#: gtkrg.ui.h:345 +msgid "Styles" +msgstr "Stili" + +#: gtkrg.ui.h:346 +msgid "Subject:" +msgstr "Oggetto:" + +#: gtkrg.ui.h:347 +msgid "Subtract dark frame" +msgstr "Sottrai riquadro scuro" + +#: gtkrg.ui.h:348 +msgid "Synchronize Tags Database" +msgstr "Sincronizza database delle etichette" + +#: gtkrg.ui.h:349 +msgid "Synchronize database" +msgstr "Sincronizza database" + +#: gtkrg.ui.h:350 +msgid "Tag Manager" +msgstr "Gestore etichette" + +#: gtkrg.ui.h:351 +msgid "Tag Manager Manual" +msgstr "Manuale del gestore etichette" + +#: gtkrg.ui.h:352 +msgid "Tag manager" +msgstr "Gestore etichette" + +#: gtkrg.ui.h:353 +msgid "Tag:" +msgstr "Etichetta:" + +#: gtkrg.ui.h:355 +msgid "Temperature" +msgstr "Temperatura colore" + +#: gtkrg.ui.h:356 +msgid "Template:" +msgstr "Template:" + +#: gtkrg.ui.h:357 +msgid "" +"The Tag Manager is a powerfull tool permitting to edit metadata information " +"of selected images. \n" +"It consist on two pages: Keywords and Advanced Metadata Editor.\n" +"\n" +"The \"Keywords\" page is for fast keywords management. It contains a generic " +"list\n" +"of categories that can be costumized as you like.\n" +"The \"New Keyword\" button adds a new keyword to the list;\n" +"The \"Remove Keywords\" button removes all checked keywords from selected " +"images;\n" +"The \"Clear Keywords\" removes all keywords from selected images.\n" +"To add keywords, check any keyword from list and click \"Apply Tags\".\n" +"Keep in mind that to write image metadata the \"write iptc metadata\" " +"option\n" +"on GTKRawGallery preferences has to be checked, otherwise keywords will be " +"stored\n" +"only into the Sqlite database for fast search. That can be the right choice\n" +"for users not interested to keywords portability.\n" +"\n" +"The \"Advanced Metadata Editor\" was designed thinking to advanced users\n" +"and permits to edit a huge list of metadata. Exif, Iptc and several Xmp " +"groups are supported.\n" +"To build a list of tags:\n" +"1) Select a group;\n" +"2) Select a tag;\n" +"3) Click \"Add\";\n" +"Repeate again to add new tags.\n" +"Finally click \"Apply Tags\" to write metadata.\n" +"Unlike keyword tagging, metadata is always written!\n" +"To avoid rebuilding the same tag list, you can save it as a template to " +"quickly reloading later." +msgstr "" +"Il gestore delle etichette è uno strumento potente che permette di editare i " +"metadati delle immagini selezionate.\n" +"E' diviso in due pagine: una per le Etichette e l'altra per l'Editor " +"avanzato dei metadati.\n" +"La prima pagina serve per una gestione rapida delle etichette e contiene una " +"lista generica che può essere personalizzata.\n" +"Il pulsante \"Nuova etichetta\" aggiunge una nuova etichetta alla lista;\n" +"Il pulsante \"Rimuovi etichette\" rimuove tutte le etichette selezionate " +"dalle immagini;\n" +"Il pulsante \"Pulisci etichette\" rimuove tutte le etichette dalle " +"immagini;\n" +"Per aggiungere le etichette, spunta le etichette dalla lista e premi " +"\"Applica etichette\".\n" +"Tieni presente che se vuoi scrivere le etichette sul file devi spuntare " +"l'opzione \"scrivi nei metadati iptc\"\n" +"nelle preferenze altrimenti le etichette verranno salvate solo sul database " +"del programma.\n" +"Questa potrebbe essere la scelta giusta per gli utenti non interessati alla " +"portabilità delle etichette.\n" +"L'Editor Avanzato dei Metadati è stato disegnato pensando agli utenti più " +"esigenti\n" +"in quanto permette di editare una quantità enorme di metadati. Sono " +"supportati i dati Exif, Iptc e diversi gruppi Xmp.\n" +"Per creare una nuova lista:\n" +"1) selezionare un gruppo;\n" +"2) selezionare un tag;\n" +"3) premere il pulsante \"Aggiungi\";\n" +"Ripetere i punti precedenti per aggiungere nuovi tag.\n" +"Infine premere \"Applica tag\" per scrivere i metadati.\n" +"In questo caso i metadati sono sempre scritti sul file.\n" +"La lista può essere salvata in un template." + +#: gtkrg.ui.h:383 +msgid "Thumbnails" +msgstr "Anteprime" + +#: gtkrg.ui.h:384 +msgid "Thumbnails Autorotation" +msgstr "Rotazione automatica delle anteprime" + +#: gtkrg.ui.h:386 +msgid "To:" +msgstr "A:" + +#: gtkrg.ui.h:387 +msgid "Tone" +msgstr "Tono" + +#: gtkrg.ui.h:388 +msgid "Total size:" +msgstr "Dimensioni totali:" + +#: gtkrg.ui.h:389 +msgid "Transform" +msgstr "Trasforma" + +#: gtkrg.ui.h:390 +msgid "Ts:" +msgstr "Ts:" + +#: gtkrg.ui.h:391 +msgid "Type:" +msgstr "Tipo:" + +#: gtkrg.ui.h:392 +msgid "Undo Workflow" +msgstr "workflow indietro" + +#: gtkrg.ui.h:393 +msgid "Uniform border" +msgstr "Bordo uniforme" + +#: gtkrg.ui.h:394 +msgid "Unselect" +msgstr "Deseleziona" + +#: gtkrg.ui.h:395 +msgid "Unsharp Mask" +msgstr "Maschera di contrasto" + +#: gtkrg.ui.h:396 +msgid "Unsplit" +msgstr "Immagine singola" + +#: gtkrg.ui.h:397 +msgid "Upload to DropBox" +msgstr "Carica su DropBox" + +#: gtkrg.ui.h:398 +msgid "Use embedded color matrix" +msgstr "Usa matrice colore interna" + +#: gtkrg.ui.h:399 +msgid "User:" +msgstr "User:" + +#: gtkrg.ui.h:400 +msgid "Vertical" +msgstr "Verticale" + +#: gtkrg.ui.h:401 +msgid "Vertical split" +msgstr "Split verticale" + +#: gtkrg.ui.h:402 +msgid "Vignette Removal" +msgstr "Rimozione vignettatura" + +#: gtkrg.ui.h:404 +msgid "White Balance" +msgstr "Bilanciamento del bianco" + +#: gtkrg.ui.h:405 +msgid "Workflow" +msgstr "Workflow" + +#: gtkrg.ui.h:406 +msgid "Write keywords as iptc metadata" +msgstr "Scrivi le etichette nei metadati iptc" + +#: gtkrg.ui.h:407 +msgid "Write metadata" +msgstr "Scrivi metadati" + +#: gtkrg.ui.h:408 +msgid "Xmp" +msgstr "Xmp" + +#: gtkrg.ui.h:409 +msgid "Yellow" +msgstr "Giallo" + +#: gtkrg.ui.h:410 +msgid "Zoom 100" +msgstr "Dimensione intera" + +#: gtkrg.ui.h:411 +msgid "Zoom Fit" +msgstr "Adatta alla finestra" + +#: gtkrg.ui.h:412 +msgid "Zoom In" +msgstr "Ingrandisci" + +#: gtkrg.ui.h:413 +msgid "Zoom Out" +msgstr "Rimpicciolisci" + +#: gtkrg.ui.h:414 +msgid "_Album" +msgstr "_Album" + +#: gtkrg.ui.h:415 +msgid "_Edit" +msgstr "_Modifica" + +#: gtkrg.ui.h:416 +msgid "_File" +msgstr "_File" + +#: gtkrg.ui.h:417 +msgid "_Help" +msgstr "_Aiuto" + +#: gtkrg.ui.h:418 +msgid "_Options" +msgstr "_Opzioni" + +#: gtkrg.ui.h:419 +msgid "_Tags" +msgstr "_Etichette" + +#: gtkrg.ui.h:420 +msgid "_View" +msgstr "_Visualizza" + +#: gtkrg.ui.h:421 +msgid "amount" +msgstr "ammontare" + +#: gtkrg.ui.h:422 +msgid "angle" +msgstr "angolo" + +#: gtkrg.ui.h:423 +msgid "apply tags" +msgstr "applica etichette" + +#: gtkrg.ui.h:424 +msgid "blue:" +msgstr "blu:" + +#: gtkrg.ui.h:425 +msgid "bmp" +msgstr "bmp" + +#: gtkrg.ui.h:426 +msgid "by album" +msgstr "per album" + +#: gtkrg.ui.h:427 +msgid "by folder" +msgstr "per cartella" + +#: gtkrg.ui.h:428 +msgid "current height" +msgstr "altezza attuale" + +#: gtkrg.ui.h:429 +msgid "current width" +msgstr "larghezza attuale" + +#: gtkrg.ui.h:430 +msgid "custom" +msgstr "personalizzato" + +#: gtkrg.ui.h:431 +msgid "date" +msgstr "data" + +#: gtkrg.ui.h:432 +msgid "default settings" +msgstr "impostazioni predefinite" + +#: gtkrg.ui.h:433 +msgid "dither" +msgstr "retinatura" + +#: gtkrg.ui.h:434 +msgid "frames:" +msgstr "frames:" + +#: gtkrg.ui.h:435 +msgid "fullscreen" +msgstr "Schermo intero" + +#: gtkrg.ui.h:436 +msgid "insert colon separated tags" +msgstr "inserire etichette separate dalla virgola" + +#: gtkrg.ui.h:437 +msgid "insert colon separated tags and click apply" +msgstr "inserire etichette separate dalla virgola a premere applica" + +#: gtkrg.ui.h:438 +msgid "jpg" +msgstr "jpg" + +#: gtkrg.ui.h:439 +msgid "label" +msgstr "etichetta" + +#: gtkrg.ui.h:440 +msgid "levels" +msgstr "Livelli" + +#: gtkrg.ui.h:441 +msgid "minimize column spacings" +msgstr "minimizza spazio fra le colonne" + +#: gtkrg.ui.h:442 +msgid "multiple search" +msgstr "ricerca multipla" + +#: gtkrg.ui.h:443 +msgid "multipliers" +msgstr "moltiplicatori" + +#: gtkrg.ui.h:444 +msgid "name" +msgstr "nome" + +#: gtkrg.ui.h:445 +msgid "only grayscale pixels" +msgstr "solo pixel grigi" + +#: gtkrg.ui.h:446 +msgid "page 1" +msgstr "pagina 1" + +#: gtkrg.ui.h:447 +msgid "page 2" +msgstr "pagina 2" + +#: gtkrg.ui.h:448 +msgid "page 3" +msgstr "pagina 3" + +#: gtkrg.ui.h:449 +msgid "page 4" +msgstr "pagina 4" + +#: gtkrg.ui.h:450 +msgid "png" +msgstr "png" + +#: gtkrg.ui.h:451 +msgid "preserve ratio" +msgstr "mantieni proporzioni" + +#: gtkrg.ui.h:452 +msgid "radius" +msgstr "raggio" + +#: gtkrg.ui.h:453 +msgid "random" +msgstr "random" + +#: gtkrg.ui.h:454 +msgid "ratio" +msgstr "rapporto" + +#: gtkrg.ui.h:455 +msgid "red:" +msgstr "rosso:" + +#: gtkrg.ui.h:456 +msgid "remove from list" +msgstr "rimuovi dalla lista" + +#: gtkrg.ui.h:457 +msgid "remove preset" +msgstr "rimuovi preset" + +#: gtkrg.ui.h:458 +msgid "remove style" +msgstr "rimuovi stile" + +#: gtkrg.ui.h:459 +msgid "reverse" +msgstr "inversa" + +#: gtkrg.ui.h:460 +msgid "save new preset" +msgstr "salva nuovo preset" + +#: gtkrg.ui.h:461 +msgid "save preset changes" +msgstr "salva modifiche preset" + +#: gtkrg.ui.h:463 +msgid "save style changes" +msgstr "salva modifiche stile" + +#: gtkrg.ui.h:464 +msgid "scan subfolders" +msgstr "cerca nelle sottocartelle" + +#: gtkrg.ui.h:465 +msgid "show grid" +msgstr "mostra griglia" + +#: gtkrg.ui.h:466 +msgid "sigma" +msgstr "sigma" + +#: gtkrg.ui.h:467 +msgid "size" +msgstr "dimensione" + +#: gtkrg.ui.h:468 +msgid "threshold" +msgstr "soglia" + +#: gtkrg.ui.h:469 +msgid "tiff 16 bit" +msgstr "tiff 16 bit" + +#: gtkrg.ui.h:470 +msgid "tiff 8 bit" +msgstr "tiff 8 bit" + +#: gtkrg.ui.h:471 +msgid "tiff best" +msgstr "tiff best" + +#: gtkrg.ui.h:472 +msgid "to" +msgstr "a" + +#: gtkrg.ui.h:473 +msgid "toolbutton13" +msgstr "" + +#: gtkrg.ui.h:474 +msgid "toolbutton36" +msgstr "ruota a " + +#: gtkrg.ui.h:475 +msgid "toolbutton47" +msgstr "" + +#: gtkrg.ui.h:476 +msgid "toolbutton48" +msgstr "" + +#: gtkrg.ui.h:477 +msgid "type" +msgstr "tipo" + +#: gtkrg.ui.h:478 +msgid "type:" +msgstr "tipo:" + +#: gtkrg.ui.h:479 +msgid "use sicure authentication" +msgstr "usa autenticazione sicura" + +#: print.ui.h:1 +msgid "Margins" +msgstr "Margini" + +#: print.ui.h:2 +msgid "Paper" +msgstr "Carta" + +#: print.ui.h:3 +msgid "Preview" +msgstr "Anteprima" + +#: print.ui.h:4 +msgid "Bottom:" +msgstr "Inferiore:" + +#: print.ui.h:5 +msgid "Custom" +msgstr "Personalizzato" + +#: print.ui.h:6 +msgid "Format:" +msgstr "Formato:" + +#: print.ui.h:7 +msgid "Landscape" +msgstr "Panoramico" + +#: print.ui.h:8 +msgid "Left:" +msgstr "Sinistro:" + +#: print.ui.h:9 +msgid "Orientation:" +msgstr "Orientamento:" + +#: print.ui.h:10 +msgid "Portrait" +msgstr "Ritratto" + +#: print.ui.h:11 +msgid "Right:" +msgstr "Destro:" + +#: print.ui.h:12 +msgid "Scale:" +msgstr "Scala:" + +#: print.ui.h:13 +msgid "Size:" +msgstr "Dimensione:" + +#: print.ui.h:14 +msgid "Top:" +msgstr "Superiore:" + +#: print.ui.h:15 +msgid "Unit:" +msgstr "Unità:" + +#: print.ui.h:16 +msgid "height:" +msgstr "altezza:" + +#: print.ui.h:17 +msgid "width:" +msgstr "larghezza:" + +#: publisher.ui.h:1 +msgid "Album description" +msgstr "Descrizione album" + +#: publisher.ui.h:2 +msgid "Album privacy" +msgstr "Privacy album" + +#: publisher.ui.h:3 +msgid "Autoscale" +msgstr "Scala automaticamente" + +#: publisher.ui.h:4 +msgid "Autoscale images to max 720 pixels preserving aspect ratio " +msgstr "" +"Scala immagini a una dimensione massima di 720 pixel preservandone le " +"proporzioni" + +#: publisher.ui.h:6 +msgid "Create a new album" +msgstr "Crea un nuovo album" + +#: publisher.ui.h:7 +msgid "Description:" +msgstr "Descrizione:" + +#: publisher.ui.h:8 +msgid "E-mail:" +msgstr "E-mail:" + +#: publisher.ui.h:9 +msgid "Facebook username:" +msgstr "Facebook username:" + +#: publisher.ui.h:11 +msgid "Log-in as different user" +msgstr "Accedi con un altro nome utente" + +#: publisher.ui.h:12 +msgid "Login" +msgstr "Accesso" + +#: publisher.ui.h:14 +msgid "New login" +msgstr "Nuovo login" + +#: publisher.ui.h:16 +msgid "Privacy:" +msgstr "Privacy:" + +#: publisher.ui.h:18 +msgid "Upload into" +msgstr "Carica su" + +#: publisher.ui.h:19 +msgid "Upload into an existing album" +msgstr "Carica in un album esistente" + +#: publisher.ui.h:20 +msgid "commentable" +msgstr "commentabile" + +#: publisher.ui.h:21 +msgid "enable album commenting" +msgstr "abilita commenti album" + +#: publisher.ui.h:22 +msgid "public" +msgstr "pubblico" + +#~ msgid "" +#~ "
Please, Log into Dropbox to authorize GTKRawGallery and click OK." +#~ msgstr "" +#~ "
Effettua l'accesso a Dropbox per autorizzare GTKRawGallery e premi " +#~ "OK
" + +#~ msgid "" +#~ "
Log into your Facebook to authorize GTKRawGallery and click OK.
" +#~ msgstr "" +#~ "
Effettua l'accesso a Facebook per autorizzare GTKRawGallery e premi " +#~ "OK
" + +#~ msgid "
Loading Facebook authenticating page...
" +#~ msgstr "
Sto caricando la pagina di autenticazione di Facebook...
" + +#~ msgid "" +#~ "
Please, Log into Flickr to authorize GTKRawGallery and click OK.If you are not redirected to the authorization page try to Sign in " +#~ "with the top right link.

Click on continue.

" +#~ msgstr "" +#~ "
Accedi a Flickr per autorizzare GTKRawGallery e premi il pulsante OK." +#~ "
Se il reindirizzamento automatico alla pagina di autenticazione non " +#~ "dovesse funzionare, prova il link in alto a destra.

Fai click su " +#~ "Continua.

" + +#~ msgid "Upload into %s DropBox" +#~ msgstr "Carica su %s DropBox" + +#~ msgid "
ERROR LOADING PAGE
" +#~ msgstr "
ERRORE DI CARICAMENTO PAGINA
" + +#~ msgid "There are %d images" +#~ msgstr "Ci sono %d immagini" + +#~ msgid "contrast" +#~ msgstr "Contrasto" + +#~ msgid "scale" +#~ msgstr "Scala" + +#~ msgid "flip" +#~ msgstr "Ribalta verticalmente" + +#~ msgid "flop" +#~ msgstr "Ribalta orizzontalmente" + +#~ msgid "border" +#~ msgstr "Bordo" + +#~ msgid "crop" +#~ msgstr "Ritaglia" + +#~ msgid "sharpen" +#~ msgstr "Affilatura" + +#~ msgid "blur" +#~ msgstr "Sfuma" + +#~ msgid "charcoal" +#~ msgstr "Carboncino" + +#~ msgid "sketch" +#~ msgstr "Schizzo" + +#~ msgid "posterize" +#~ msgstr "Posterizza" + +#~ msgid "negate" +#~ msgstr "Negativo" + +#~ msgid "median filter" +#~ msgstr "Filtro mediano" + +#~ msgid "normalize" +#~ msgstr "Normalizza" + +#~ msgid "spread" +#~ msgstr "Spruzzo" + +#~ msgid "curve" +#~ msgstr "Curve" + +#~ msgid "tint" +#~ msgstr "Tinta" + +#~ msgid "Apply workflow" +#~ msgstr "Applica workflow" + +#~ msgid "English" +#~ msgstr "Inglese" + +#~ msgid "Italian" +#~ msgstr "Italiano" + +#~ msgid "Language" +#~ msgstr "Lingua" + +#~ msgid "Set application language" +#~ msgstr "Imposta la lingua" + +#~ msgid "The workflow was saved for next conversion!" +#~ msgstr "Il workflow è stato salvato per la prossima conversione." + +#~ msgid "0" +#~ msgstr "0" + +#~ msgid "100" +#~ msgstr "100" diff -Nru gtkrawgallery-0.9.8/src/mechanize/COPYING.txt gtkrawgallery-0.9.9/src/mechanize/COPYING.txt --- gtkrawgallery-0.9.8/src/mechanize/COPYING.txt 2013-03-21 05:08:38.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/COPYING.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -All the code with the exception of _gzip.py is covered under either -the BSD-style license immediately below, or (at your choice) the ZPL -2.1. The code in _gzip.py is taken from the effbot.org library, and -falls under the effbot.org license (also BSD-style) that appears at -the end of this file. - - -Copyright (c) 2002-2010 John J. Lee -Copyright (c) 1997-1999 Gisle Aas -Copyright (c) 1997-1999 Johnny Lee -Copyright (c) 2003 Andy Lester - - -BSD-style License -================== - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -Neither the name of the contributors nor the names of their employers -may be used to endorse or promote products derived from this software -without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - -ZPL 2.1 -================== - -Zope Public License (ZPL) Version 2.1 - -A copyright notice accompanies this license document that identifies the copyright holders. - -This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. - 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. - 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. - 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. - -Disclaimer - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - --------------------------------------------------------------------- -The effbot.org Library is - -Copyright (c) 1999-2004 by Secret Labs AB -Copyright (c) 1999-2004 by Fredrik Lundh - -By obtaining, using, and/or copying this software and/or its -associated documentation, you agree that you have read, understood, -and will comply with the following terms and conditions: - -Permission to use, copy, modify, and distribute this software and its -associated documentation for any purpose and without fee is hereby -granted, provided that the above copyright notice appears in all -copies, and that both that copyright notice and this permission notice -appear in supporting documentation, and that the name of Secret Labs -AB or the author not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR -ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --------------------------------------------------------------------- diff -Nru gtkrawgallery-0.9.8/src/mechanize/__init__.py gtkrawgallery-0.9.9/src/mechanize/__init__.py --- gtkrawgallery-0.9.8/src/mechanize/__init__.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,211 +0,0 @@ -__all__ = [ - 'AbstractBasicAuthHandler', - 'AbstractDigestAuthHandler', - 'BaseHandler', - 'Browser', - 'BrowserStateError', - 'CacheFTPHandler', - 'ContentTooShortError', - 'Cookie', - 'CookieJar', - 'CookiePolicy', - 'DefaultCookiePolicy', - 'DefaultFactory', - 'FTPHandler', - 'Factory', - 'FileCookieJar', - 'FileHandler', - 'FormNotFoundError', - 'FormsFactory', - 'HTTPBasicAuthHandler', - 'HTTPCookieProcessor', - 'HTTPDefaultErrorHandler', - 'HTTPDigestAuthHandler', - 'HTTPEquivProcessor', - 'HTTPError', - 'HTTPErrorProcessor', - 'HTTPHandler', - 'HTTPPasswordMgr', - 'HTTPPasswordMgrWithDefaultRealm', - 'HTTPProxyPasswordMgr', - 'HTTPRedirectDebugProcessor', - 'HTTPRedirectHandler', - 'HTTPRefererProcessor', - 'HTTPRefreshProcessor', - 'HTTPResponseDebugProcessor', - 'HTTPRobotRulesProcessor', - 'HTTPSClientCertMgr', - 'HeadParser', - 'History', - 'LWPCookieJar', - 'Link', - 'LinkNotFoundError', - 'LinksFactory', - 'LoadError', - 'MSIECookieJar', - 'MozillaCookieJar', - 'OpenerDirector', - 'OpenerFactory', - 'ParseError', - 'ProxyBasicAuthHandler', - 'ProxyDigestAuthHandler', - 'ProxyHandler', - 'Request', - 'RobotExclusionError', - 'RobustFactory', - 'RobustFormsFactory', - 'RobustLinksFactory', - 'RobustTitleFactory', - 'SeekableResponseOpener', - 'TitleFactory', - 'URLError', - 'USE_BARE_EXCEPT', - 'UnknownHandler', - 'UserAgent', - 'UserAgentBase', - 'XHTMLCompatibleHeadParser', - '__version__', - 'build_opener', - 'install_opener', - 'lwp_cookie_str', - 'make_response', - 'request_host', - 'response_seek_wrapper', # XXX deprecate in public interface? - 'seek_wrapped_response', # XXX should probably use this internally in place of response_seek_wrapper() - 'str2time', - 'urlopen', - 'urlretrieve', - 'urljoin', - - # ClientForm API - 'AmbiguityError', - 'ControlNotFoundError', - 'FormParser', - 'ItemCountError', - 'ItemNotFoundError', - 'LocateError', - 'Missing', - 'ParseFile', - 'ParseFileEx', - 'ParseResponse', - 'ParseResponseEx', - 'ParseString', - 'XHTMLCompatibleFormParser', - # deprecated - 'CheckboxControl', - 'Control', - 'FileControl', - 'HTMLForm', - 'HiddenControl', - 'IgnoreControl', - 'ImageControl', - 'IsindexControl', - 'Item', - 'Label', - 'ListControl', - 'PasswordControl', - 'RadioControl', - 'ScalarControl', - 'SelectControl', - 'SubmitButtonControl', - 'SubmitControl', - 'TextControl', - 'TextareaControl', - ] - -import logging -import sys - -from _version import __version__ - -# high-level stateful browser-style interface -from _mechanize import \ - Browser, History, \ - BrowserStateError, LinkNotFoundError, FormNotFoundError - -# configurable URL-opener interface -from _useragent import UserAgentBase, UserAgent -from _html import \ - Link, \ - Factory, DefaultFactory, RobustFactory, \ - FormsFactory, LinksFactory, TitleFactory, \ - RobustFormsFactory, RobustLinksFactory, RobustTitleFactory - -# urllib2 work-alike interface. This is a superset of the urllib2 interface. -from _urllib2 import * -import _urllib2 -if hasattr(_urllib2, "HTTPSHandler"): - __all__.append("HTTPSHandler") -del _urllib2 - -# misc -from _http import HeadParser -from _http import XHTMLCompatibleHeadParser -from _opener import ContentTooShortError, OpenerFactory, urlretrieve -from _response import \ - response_seek_wrapper, seek_wrapped_response, make_response -from _rfc3986 import urljoin -from _util import http2time as str2time - -# cookies -from _clientcookie import Cookie, CookiePolicy, DefaultCookiePolicy, \ - CookieJar, FileCookieJar, LoadError, request_host_lc as request_host, \ - effective_request_host -from _lwpcookiejar import LWPCookieJar, lwp_cookie_str -# 2.4 raises SyntaxError due to generator / try/finally use -if sys.version_info[:2] > (2,4): - try: - import sqlite3 - except ImportError: - pass - else: - from _firefox3cookiejar import Firefox3CookieJar -from _mozillacookiejar import MozillaCookieJar -from _msiecookiejar import MSIECookieJar - -# forms -from _form import ( - AmbiguityError, - ControlNotFoundError, - FormParser, - ItemCountError, - ItemNotFoundError, - LocateError, - Missing, - ParseError, - ParseFile, - ParseFileEx, - ParseResponse, - ParseResponseEx, - ParseString, - XHTMLCompatibleFormParser, - # deprecated - CheckboxControl, - Control, - FileControl, - HTMLForm, - HiddenControl, - IgnoreControl, - ImageControl, - IsindexControl, - Item, - Label, - ListControl, - PasswordControl, - RadioControl, - ScalarControl, - SelectControl, - SubmitButtonControl, - SubmitControl, - TextControl, - TextareaControl, - ) - -# If you hate the idea of turning bugs into warnings, do: -# import mechanize; mechanize.USE_BARE_EXCEPT = False -USE_BARE_EXCEPT = True - -logger = logging.getLogger("mechanize") -if logger.level is logging.NOTSET: - logger.setLevel(logging.CRITICAL) -del logger diff -Nru gtkrawgallery-0.9.8/src/mechanize/_auth.py gtkrawgallery-0.9.9/src/mechanize/_auth.py --- gtkrawgallery-0.9.8/src/mechanize/_auth.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/_auth.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -"""HTTP Authentication and Proxy support. - - -Copyright 2006 John J. Lee - -This code is free software; you can redistribute it and/or modify it under -the terms of the BSD or ZPL 2.1 licenses (see the file COPYING.txt -included with the distribution). - -""" - -from _urllib2_fork import HTTPPasswordMgr - - -# TODO: stop deriving from HTTPPasswordMgr -class HTTPProxyPasswordMgr(HTTPPasswordMgr): - # has default realm and host/port - def add_password(self, realm, uri, user, passwd): - # uri could be a single URI or a sequence - if uri is None or isinstance(uri, basestring): - uris = [uri] - else: - uris = uri - passwd_by_domain = self.passwd.setdefault(realm, {}) - for uri in uris: - for default_port in True, False: - reduced_uri = self.reduce_uri(uri, default_port) - passwd_by_domain[reduced_uri] = (user, passwd) - - def find_user_password(self, realm, authuri): - attempts = [(realm, authuri), (None, authuri)] - # bleh, want default realm to take precedence over default - # URI/authority, hence this outer loop - for default_uri in False, True: - for realm, authuri in attempts: - authinfo_by_domain = self.passwd.get(realm, {}) - for default_port in True, False: - reduced_authuri = self.reduce_uri(authuri, default_port) - for uri, authinfo in authinfo_by_domain.iteritems(): - if uri is None and not default_uri: - continue - if self.is_suburi(uri, reduced_authuri): - return authinfo - user, password = None, None - - if user is not None: - break - return user, password - - def reduce_uri(self, uri, default_port=True): - if uri is None: - return None - return HTTPPasswordMgr.reduce_uri(self, uri, default_port) - - def is_suburi(self, base, test): - if base is None: - # default to the proxy's host/port - hostport, path = test - base = (hostport, "/") - return HTTPPasswordMgr.is_suburi(self, base, test) - - -class HTTPSClientCertMgr(HTTPPasswordMgr): - # implementation inheritance: this is not a proper subclass - def add_key_cert(self, uri, key_file, cert_file): - self.add_password(None, uri, key_file, cert_file) - def find_key_cert(self, authuri): - return HTTPPasswordMgr.find_user_password(self, None, authuri) diff -Nru gtkrawgallery-0.9.8/src/mechanize/_beautifulsoup.py gtkrawgallery-0.9.9/src/mechanize/_beautifulsoup.py --- gtkrawgallery-0.9.8/src/mechanize/_beautifulsoup.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/_beautifulsoup.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1077 +0,0 @@ -"""Beautiful Soup -Elixir and Tonic -"The Screen-Scraper's Friend" -v2.1.1 -http://www.crummy.com/software/BeautifulSoup/ - -Beautiful Soup parses arbitrarily invalid XML- or HTML-like substance -into a tree representation. It provides methods and Pythonic idioms -that make it easy to search and modify the tree. - -A well-formed XML/HTML document will yield a well-formed data -structure. An ill-formed XML/HTML document will yield a -correspondingly ill-formed data structure. If your document is only -locally well-formed, you can use this library to find and process the -well-formed part of it. The BeautifulSoup class has heuristics for -obtaining a sensible parse tree in the face of common HTML errors. - -Beautiful Soup has no external dependencies. It works with Python 2.2 -and up. - -Beautiful Soup defines classes for four different parsing strategies: - - * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific - language that kind of looks like XML. - - * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid - or invalid. - - * ICantBelieveItsBeautifulSoup, for parsing valid but bizarre HTML - that trips up BeautifulSoup. - - * BeautifulSOAP, for making it easier to parse XML documents that use - lots of subelements containing a single string, where you'd prefer - they put that string into an attribute (such as SOAP messages). - -You can subclass BeautifulStoneSoup or BeautifulSoup to create a -parsing strategy specific to an XML schema or a particular bizarre -HTML document. Typically your subclass would just override -SELF_CLOSING_TAGS and/or NESTABLE_TAGS. -""" #" -from __future__ import generators - -__author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "2.1.1" -__date__ = "$Date: 2004/10/18 00:14:20 $" -__copyright__ = "Copyright (c) 2004-2005 Leonard Richardson" -__license__ = "PSF" - -from _sgmllib_copy import SGMLParser, SGMLParseError -import types -import re -import _sgmllib_copy as sgmllib - -class NullType(object): - - """Similar to NoneType with a corresponding singleton instance - 'Null' that, unlike None, accepts any message and returns itself. - - Examples: - >>> Null("send", "a", "message")("and one more", - ... "and what you get still") is Null - True - """ - - def __new__(cls): return Null - def __call__(self, *args, **kwargs): return Null -## def __getstate__(self, *args): return Null - def __getattr__(self, attr): return Null - def __getitem__(self, item): return Null - def __setattr__(self, attr, value): pass - def __setitem__(self, item, value): pass - def __len__(self): return 0 - # FIXME: is this a python bug? otherwise ``for x in Null: pass`` - # never terminates... - def __iter__(self): return iter([]) - def __contains__(self, item): return False - def __repr__(self): return "Null" -Null = object.__new__(NullType) - -class PageElement: - """Contains the navigational information for some part of the page - (either a tag or a piece of text)""" - - def setup(self, parent=Null, previous=Null): - """Sets up the initial relations between this element and - other elements.""" - self.parent = parent - self.previous = previous - self.next = Null - self.previousSibling = Null - self.nextSibling = Null - if self.parent and self.parent.contents: - self.previousSibling = self.parent.contents[-1] - self.previousSibling.nextSibling = self - - def findNext(self, name=None, attrs={}, text=None): - """Returns the first item that matches the given criteria and - appears after this Tag in the document.""" - return self._first(self.fetchNext, name, attrs, text) - firstNext = findNext - - def fetchNext(self, name=None, attrs={}, text=None, limit=None): - """Returns all items that match the given criteria and appear - before after Tag in the document.""" - return self._fetch(name, attrs, text, limit, self.nextGenerator) - - def findNextSibling(self, name=None, attrs={}, text=None): - """Returns the closest sibling to this Tag that matches the - given criteria and appears after this Tag in the document.""" - return self._first(self.fetchNextSiblings, name, attrs, text) - firstNextSibling = findNextSibling - - def fetchNextSiblings(self, name=None, attrs={}, text=None, limit=None): - """Returns the siblings of this Tag that match the given - criteria and appear after this Tag in the document.""" - return self._fetch(name, attrs, text, limit, self.nextSiblingGenerator) - - def findPrevious(self, name=None, attrs={}, text=None): - """Returns the first item that matches the given criteria and - appears before this Tag in the document.""" - return self._first(self.fetchPrevious, name, attrs, text) - - def fetchPrevious(self, name=None, attrs={}, text=None, limit=None): - """Returns all items that match the given criteria and appear - before this Tag in the document.""" - return self._fetch(name, attrs, text, limit, self.previousGenerator) - firstPrevious = findPrevious - - def findPreviousSibling(self, name=None, attrs={}, text=None): - """Returns the closest sibling to this Tag that matches the - given criteria and appears before this Tag in the document.""" - return self._first(self.fetchPreviousSiblings, name, attrs, text) - firstPreviousSibling = findPreviousSibling - - def fetchPreviousSiblings(self, name=None, attrs={}, text=None, - limit=None): - """Returns the siblings of this Tag that match the given - criteria and appear before this Tag in the document.""" - return self._fetch(name, attrs, text, limit, - self.previousSiblingGenerator) - - def findParent(self, name=None, attrs={}): - """Returns the closest parent of this Tag that matches the given - criteria.""" - r = Null - l = self.fetchParents(name, attrs, 1) - if l: - r = l[0] - return r - firstParent = findParent - - def fetchParents(self, name=None, attrs={}, limit=None): - """Returns the parents of this Tag that match the given - criteria.""" - return self._fetch(name, attrs, None, limit, self.parentGenerator) - - #These methods do the real heavy lifting. - - def _first(self, method, name, attrs, text): - r = Null - l = method(name, attrs, text, 1) - if l: - r = l[0] - return r - - def _fetch(self, name, attrs, text, limit, generator): - "Iterates over a generator looking for things that match." - if not hasattr(attrs, 'items'): - attrs = {'class' : attrs} - - results = [] - g = generator() - while True: - try: - i = g.next() - except StopIteration: - break - found = None - if isinstance(i, Tag): - if not text: - if not name or self._matches(i, name): - match = True - for attr, matchAgainst in attrs.items(): - check = i.get(attr) - if not self._matches(check, matchAgainst): - match = False - break - if match: - found = i - elif text: - if self._matches(i, text): - found = i - if found: - results.append(found) - if limit and len(results) >= limit: - break - return results - - #Generators that can be used to navigate starting from both - #NavigableTexts and Tags. - def nextGenerator(self): - i = self - while i: - i = i.next - yield i - - def nextSiblingGenerator(self): - i = self - while i: - i = i.nextSibling - yield i - - def previousGenerator(self): - i = self - while i: - i = i.previous - yield i - - def previousSiblingGenerator(self): - i = self - while i: - i = i.previousSibling - yield i - - def parentGenerator(self): - i = self - while i: - i = i.parent - yield i - - def _matches(self, chunk, howToMatch): - #print 'looking for %s in %s' % (howToMatch, chunk) - # - # If given a list of items, return true if the list contains a - # text element that matches. - if isList(chunk) and not isinstance(chunk, Tag): - for tag in chunk: - if isinstance(tag, NavigableText) and self._matches(tag, howToMatch): - return True - return False - if callable(howToMatch): - return howToMatch(chunk) - if isinstance(chunk, Tag): - #Custom match methods take the tag as an argument, but all other - #ways of matching match the tag name as a string - chunk = chunk.name - #Now we know that chunk is a string - if not isinstance(chunk, basestring): - chunk = str(chunk) - if hasattr(howToMatch, 'match'): - # It's a regexp object. - return howToMatch.search(chunk) - if isList(howToMatch): - return chunk in howToMatch - if hasattr(howToMatch, 'items'): - return howToMatch.has_key(chunk) - #It's just a string - return str(howToMatch) == chunk - -class NavigableText(PageElement): - - def __getattr__(self, attr): - "For backwards compatibility, text.string gives you text" - if attr == 'string': - return self - else: - raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr) - -class NavigableString(str, NavigableText): - pass - -class NavigableUnicodeString(unicode, NavigableText): - pass - -class Tag(PageElement): - - """Represents a found HTML tag with its attributes and contents.""" - - def __init__(self, name, attrs=None, parent=Null, previous=Null): - "Basic constructor." - self.name = name - if attrs == None: - attrs = [] - self.attrs = attrs - self.contents = [] - self.setup(parent, previous) - self.hidden = False - - def get(self, key, default=None): - """Returns the value of the 'key' attribute for the tag, or - the value given for 'default' if it doesn't have that - attribute.""" - return self._getAttrMap().get(key, default) - - def __getitem__(self, key): - """tag[key] returns the value of the 'key' attribute for the tag, - and throws an exception if it's not there.""" - return self._getAttrMap()[key] - - def __iter__(self): - "Iterating over a tag iterates over its contents." - return iter(self.contents) - - def __len__(self): - "The length of a tag is the length of its list of contents." - return len(self.contents) - - def __contains__(self, x): - return x in self.contents - - def __nonzero__(self): - "A tag is non-None even if it has no contents." - return True - - def __setitem__(self, key, value): - """Setting tag[key] sets the value of the 'key' attribute for the - tag.""" - self._getAttrMap() - self.attrMap[key] = value - found = False - for i in range(0, len(self.attrs)): - if self.attrs[i][0] == key: - self.attrs[i] = (key, value) - found = True - if not found: - self.attrs.append((key, value)) - self._getAttrMap()[key] = value - - def __delitem__(self, key): - "Deleting tag[key] deletes all 'key' attributes for the tag." - for item in self.attrs: - if item[0] == key: - self.attrs.remove(item) - #We don't break because bad HTML can define the same - #attribute multiple times. - self._getAttrMap() - if self.attrMap.has_key(key): - del self.attrMap[key] - - def __call__(self, *args, **kwargs): - """Calling a tag like a function is the same as calling its - fetch() method. Eg. tag('a') returns a list of all the A tags - found within this tag.""" - return apply(self.fetch, args, kwargs) - - def __getattr__(self, tag): - if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3: - return self.first(tag[:-3]) - elif tag.find('__') != 0: - return self.first(tag) - - def __eq__(self, other): - """Returns true iff this tag has the same name, the same attributes, - and the same contents (recursively) as the given tag. - - NOTE: right now this will return false if two tags have the - same attributes in a different order. Should this be fixed?""" - if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other): - return False - for i in range(0, len(self.contents)): - if self.contents[i] != other.contents[i]: - return False - return True - - def __ne__(self, other): - """Returns true iff this tag is not identical to the other tag, - as defined in __eq__.""" - return not self == other - - def __repr__(self): - """Renders this tag as a string.""" - return str(self) - - def __unicode__(self): - return self.__str__(1) - - def __str__(self, needUnicode=None, showStructureIndent=None): - """Returns a string or Unicode representation of this tag and - its contents. - - NOTE: since Python's HTML parser consumes whitespace, this - method is not certain to reproduce the whitespace present in - the original string.""" - - attrs = [] - if self.attrs: - for key, val in self.attrs: - attrs.append('%s="%s"' % (key, val)) - close = '' - closeTag = '' - if self.isSelfClosing(): - close = ' /' - else: - closeTag = '' % self.name - indentIncrement = None - if showStructureIndent != None: - indentIncrement = showStructureIndent - if not self.hidden: - indentIncrement += 1 - contents = self.renderContents(indentIncrement, needUnicode=needUnicode) - if showStructureIndent: - space = '\n%s' % (' ' * showStructureIndent) - if self.hidden: - s = contents - else: - s = [] - attributeString = '' - if attrs: - attributeString = ' ' + ' '.join(attrs) - if showStructureIndent: - s.append(space) - s.append('<%s%s%s>' % (self.name, attributeString, close)) - s.append(contents) - if closeTag and showStructureIndent != None: - s.append(space) - s.append(closeTag) - s = ''.join(s) - isUnicode = type(s) == types.UnicodeType - if needUnicode and not isUnicode: - s = unicode(s) - elif isUnicode and needUnicode==False: - s = str(s) - return s - - def prettify(self, needUnicode=None): - return self.__str__(needUnicode, showStructureIndent=True) - - def renderContents(self, showStructureIndent=None, needUnicode=None): - """Renders the contents of this tag as a (possibly Unicode) - string.""" - s=[] - for c in self: - text = None - if isinstance(c, NavigableUnicodeString) or type(c) == types.UnicodeType: - text = unicode(c) - elif isinstance(c, Tag): - s.append(c.__str__(needUnicode, showStructureIndent)) - elif needUnicode: - text = unicode(c) - else: - text = str(c) - if text: - if showStructureIndent != None: - if text[-1] == '\n': - text = text[:-1] - s.append(text) - return ''.join(s) - - #Soup methods - - def firstText(self, text, recursive=True): - """Convenience method to retrieve the first piece of text matching the - given criteria. 'text' can be a string, a regular expression object, - a callable that takes a string and returns whether or not the - string 'matches', etc.""" - return self.first(recursive=recursive, text=text) - - def fetchText(self, text, recursive=True, limit=None): - """Convenience method to retrieve all pieces of text matching the - given criteria. 'text' can be a string, a regular expression object, - a callable that takes a string and returns whether or not the - string 'matches', etc.""" - return self.fetch(recursive=recursive, text=text, limit=limit) - - def first(self, name=None, attrs={}, recursive=True, text=None): - """Return only the first child of this - Tag matching the given criteria.""" - r = Null - l = self.fetch(name, attrs, recursive, text, 1) - if l: - r = l[0] - return r - findChild = first - - def fetch(self, name=None, attrs={}, recursive=True, text=None, - limit=None): - """Extracts a list of Tag objects that match the given - criteria. You can specify the name of the Tag and any - attributes you want the Tag to have. - - The value of a key-value pair in the 'attrs' map can be a - string, a list of strings, a regular expression object, or a - callable that takes a string and returns whether or not the - string matches for some custom definition of 'matches'. The - same is true of the tag name.""" - generator = self.recursiveChildGenerator - if not recursive: - generator = self.childGenerator - return self._fetch(name, attrs, text, limit, generator) - fetchChildren = fetch - - #Utility methods - - def isSelfClosing(self): - """Returns true iff this is a self-closing tag as defined in the HTML - standard. - - TODO: This is specific to BeautifulSoup and its subclasses, but it's - used by __str__""" - return self.name in BeautifulSoup.SELF_CLOSING_TAGS - - def append(self, tag): - """Appends the given tag to the contents of this tag.""" - self.contents.append(tag) - - #Private methods - - def _getAttrMap(self): - """Initializes a map representation of this tag's attributes, - if not already initialized.""" - if not getattr(self, 'attrMap'): - self.attrMap = {} - for (key, value) in self.attrs: - self.attrMap[key] = value - return self.attrMap - - #Generator methods - def childGenerator(self): - for i in range(0, len(self.contents)): - yield self.contents[i] - raise StopIteration - - def recursiveChildGenerator(self): - stack = [(self, 0)] - while stack: - tag, start = stack.pop() - if isinstance(tag, Tag): - for i in range(start, len(tag.contents)): - a = tag.contents[i] - yield a - if isinstance(a, Tag) and tag.contents: - if i < len(tag.contents) - 1: - stack.append((tag, i+1)) - stack.append((a, 0)) - break - raise StopIteration - - -def isList(l): - """Convenience method that works with all 2.x versions of Python - to determine whether or not something is listlike.""" - return hasattr(l, '__iter__') \ - or (type(l) in (types.ListType, types.TupleType)) - -def buildTagMap(default, *args): - """Turns a list of maps, lists, or scalars into a single map. - Used to build the SELF_CLOSING_TAGS and NESTABLE_TAGS maps out - of lists and partial maps.""" - built = {} - for portion in args: - if hasattr(portion, 'items'): - #It's a map. Merge it. - for k,v in portion.items(): - built[k] = v - elif isList(portion): - #It's a list. Map each item to the default. - for k in portion: - built[k] = default - else: - #It's a scalar. Map it to the default. - built[portion] = default - return built - -class BeautifulStoneSoup(Tag, SGMLParser): - - """This class contains the basic parser and fetch code. It defines - a parser that knows nothing about tag behavior except for the - following: - - You can't close a tag without closing all the tags it encloses. - That is, "" actually means - "". - - [Another possible explanation is "", but since - this class defines no SELF_CLOSING_TAGS, it will never use that - explanation.] - - This class is useful for parsing XML or made-up markup languages, - or when BeautifulSoup makes an assumption counter to what you were - expecting.""" - - SELF_CLOSING_TAGS = {} - NESTABLE_TAGS = {} - RESET_NESTING_TAGS = {} - QUOTE_TAGS = {} - - #As a public service we will by default silently replace MS smart quotes - #and similar characters with their HTML or ASCII equivalents. - MS_CHARS = { '\x80' : '€', - '\x81' : ' ', - '\x82' : '‚', - '\x83' : 'ƒ', - '\x84' : '„', - '\x85' : '…', - '\x86' : '†', - '\x87' : '‡', - '\x88' : '⁁', - '\x89' : '%', - '\x8A' : 'Š', - '\x8B' : '<', - '\x8C' : 'Œ', - '\x8D' : '?', - '\x8E' : 'Z', - '\x8F' : '?', - '\x90' : '?', - '\x91' : '‘', - '\x92' : '’', - '\x93' : '“', - '\x94' : '”', - '\x95' : '•', - '\x96' : '–', - '\x97' : '—', - '\x98' : '˜', - '\x99' : '™', - '\x9a' : 'š', - '\x9b' : '>', - '\x9c' : 'œ', - '\x9d' : '?', - '\x9e' : 'z', - '\x9f' : 'Ÿ',} - - PARSER_MASSAGE = [(re.compile('(<[^<>]*)/>'), - lambda(x):x.group(1) + ' />'), - (re.compile(']*)>'), - lambda(x):''), - (re.compile("([\x80-\x9f])"), - lambda(x): BeautifulStoneSoup.MS_CHARS.get(x.group(1))) - ] - - ROOT_TAG_NAME = '[document]' - - def __init__(self, text=None, avoidParserProblems=True, - initialTextIsEverything=True): - """Initialize this as the 'root tag' and feed in any text to - the parser. - - NOTE about avoidParserProblems: sgmllib will process most bad - HTML, and BeautifulSoup has tricks for dealing with some HTML - that kills sgmllib, but Beautiful Soup can nonetheless choke - or lose data if your data uses self-closing tags or - declarations incorrectly. By default, Beautiful Soup sanitizes - its input to avoid the vast majority of these problems. The - problems are relatively rare, even in bad HTML, so feel free - to pass in False to avoidParserProblems if they don't apply to - you, and you'll get better performance. The only reason I have - this turned on by default is so I don't get so many tech - support questions. - - The two most common instances of invalid HTML that will choke - sgmllib are fixed by the default parser massage techniques: - -
(No space between name of closing tag and tag close) - (Extraneous whitespace in declaration) - - You can pass in a custom list of (RE object, replace method) - tuples to get Beautiful Soup to scrub your input the way you - want.""" - Tag.__init__(self, self.ROOT_TAG_NAME) - if avoidParserProblems \ - and not isList(avoidParserProblems): - avoidParserProblems = self.PARSER_MASSAGE - self.avoidParserProblems = avoidParserProblems - SGMLParser.__init__(self) - self.quoteStack = [] - self.hidden = 1 - self.reset() - if hasattr(text, 'read'): - #It's a file-type object. - text = text.read() - if text: - self.feed(text) - if initialTextIsEverything: - self.done() - - def __getattr__(self, methodName): - """This method routes method call requests to either the SGMLParser - superclass or the Tag superclass, depending on the method name.""" - if methodName.find('start_') == 0 or methodName.find('end_') == 0 \ - or methodName.find('do_') == 0: - return SGMLParser.__getattr__(self, methodName) - elif methodName.find('__') != 0: - return Tag.__getattr__(self, methodName) - else: - raise AttributeError - - def feed(self, text): - if self.avoidParserProblems: - for fix, m in self.avoidParserProblems: - text = fix.sub(m, text) - SGMLParser.feed(self, text) - - def done(self): - """Called when you're done parsing, so that the unclosed tags can be - correctly processed.""" - self.endData() #NEW - while self.currentTag.name != self.ROOT_TAG_NAME: - self.popTag() - - def reset(self): - SGMLParser.reset(self) - self.currentData = [] - self.currentTag = None - self.tagStack = [] - self.pushTag(self) - - def popTag(self): - tag = self.tagStack.pop() - # Tags with just one string-owning child get the child as a - # 'string' property, so that soup.tag.string is shorthand for - # soup.tag.contents[0] - if len(self.currentTag.contents) == 1 and \ - isinstance(self.currentTag.contents[0], NavigableText): - self.currentTag.string = self.currentTag.contents[0] - - #print "Pop", tag.name - if self.tagStack: - self.currentTag = self.tagStack[-1] - return self.currentTag - - def pushTag(self, tag): - #print "Push", tag.name - if self.currentTag: - self.currentTag.append(tag) - self.tagStack.append(tag) - self.currentTag = self.tagStack[-1] - - def endData(self): - currentData = ''.join(self.currentData) - if currentData: - if not currentData.strip(): - if '\n' in currentData: - currentData = '\n' - else: - currentData = ' ' - c = NavigableString - if type(currentData) == types.UnicodeType: - c = NavigableUnicodeString - o = c(currentData) - o.setup(self.currentTag, self.previous) - if self.previous: - self.previous.next = o - self.previous = o - self.currentTag.contents.append(o) - self.currentData = [] - - def _popToTag(self, name, inclusivePop=True): - """Pops the tag stack up to and including the most recent - instance of the given tag. If inclusivePop is false, pops the tag - stack up to but *not* including the most recent instqance of - the given tag.""" - if name == self.ROOT_TAG_NAME: - return - - numPops = 0 - mostRecentTag = None - for i in range(len(self.tagStack)-1, 0, -1): - if name == self.tagStack[i].name: - numPops = len(self.tagStack)-i - break - if not inclusivePop: - numPops = numPops - 1 - - for i in range(0, numPops): - mostRecentTag = self.popTag() - return mostRecentTag - - def _smartPop(self, name): - - """We need to pop up to the previous tag of this type, unless - one of this tag's nesting reset triggers comes between this - tag and the previous tag of this type, OR unless this tag is a - generic nesting trigger and another generic nesting trigger - comes between this tag and the previous tag of this type. - - Examples: -

FooBar

should pop to 'p', not 'b'. -

FooBar

should pop to 'table', not 'p'. -

Foo

Bar

should pop to 'tr', not 'p'. -

FooBar

should pop to 'p', not 'b'. - -

    • *
    • * should pop to 'ul', not the first 'li'. -
  • ** should pop to 'table', not the first 'tr' - tag should - implicitly close the previous tag within the same
    ** should pop to 'tr', not the first 'td' - """ - - nestingResetTriggers = self.NESTABLE_TAGS.get(name) - isNestable = nestingResetTriggers != None - isResetNesting = self.RESET_NESTING_TAGS.has_key(name) - popTo = None - inclusive = True - for i in range(len(self.tagStack)-1, 0, -1): - p = self.tagStack[i] - if (not p or p.name == name) and not isNestable: - #Non-nestable tags get popped to the top or to their - #last occurance. - popTo = name - break - if (nestingResetTriggers != None - and p.name in nestingResetTriggers) \ - or (nestingResetTriggers == None and isResetNesting - and self.RESET_NESTING_TAGS.has_key(p.name)): - - #If we encounter one of the nesting reset triggers - #peculiar to this tag, or we encounter another tag - #that causes nesting to reset, pop up to but not - #including that tag. - - popTo = p.name - inclusive = False - break - p = p.parent - if popTo: - self._popToTag(popTo, inclusive) - - def unknown_starttag(self, name, attrs, selfClosing=0): - #print "Start tag %s" % name - if self.quoteStack: - #This is not a real tag. - #print "<%s> is not real!" % name - attrs = ''.join(map(lambda(x, y): ' %s="%s"' % (x, y), attrs)) - self.handle_data('<%s%s>' % (name, attrs)) - return - self.endData() - if not name in self.SELF_CLOSING_TAGS and not selfClosing: - self._smartPop(name) - tag = Tag(name, attrs, self.currentTag, self.previous) - if self.previous: - self.previous.next = tag - self.previous = tag - self.pushTag(tag) - if selfClosing or name in self.SELF_CLOSING_TAGS: - self.popTag() - if name in self.QUOTE_TAGS: - #print "Beginning quote (%s)" % name - self.quoteStack.append(name) - self.literal = 1 - - def unknown_endtag(self, name): - if self.quoteStack and self.quoteStack[-1] != name: - #This is not a real end tag. - #print " is not real!" % name - self.handle_data('' % name) - return - self.endData() - self._popToTag(name) - if self.quoteStack and self.quoteStack[-1] == name: - self.quoteStack.pop() - self.literal = (len(self.quoteStack) > 0) - - def handle_data(self, data): - self.currentData.append(data) - - def handle_pi(self, text): - "Propagate processing instructions right through." - self.handle_data("" % text) - - def handle_comment(self, text): - "Propagate comments right through." - self.handle_data("" % text) - - def handle_charref(self, ref): - "Propagate char refs right through." - self.handle_data('&#%s;' % ref) - - def handle_entityref(self, ref): - "Propagate entity refs right through." - self.handle_data('&%s;' % ref) - - def handle_decl(self, data): - "Propagate DOCTYPEs and the like right through." - self.handle_data('' % data) - - def parse_declaration(self, i): - """Treat a bogus SGML declaration as raw data. Treat a CDATA - declaration as regular data.""" - j = None - if self.rawdata[i:i+9] == '', i) - if k == -1: - k = len(self.rawdata) - self.handle_data(self.rawdata[i+9:k]) - j = k+3 - else: - try: - j = SGMLParser.parse_declaration(self, i) - except SGMLParseError: - toHandle = self.rawdata[i:] - self.handle_data(toHandle) - j = i + len(toHandle) - return j - -class BeautifulSoup(BeautifulStoneSoup): - - """This parser knows the following facts about HTML: - - * Some tags have no closing tag and should be interpreted as being - closed as soon as they are encountered. - - * The text inside some tags (ie. 'script') may contain tags which - are not really part of the document and which should be parsed - as text, not tags. If you want to parse the text as tags, you can - always fetch it and parse it explicitly. - - * Tag nesting rules: - - Most tags can't be nested at all. For instance, the occurance of - a

    tag should implicitly close the previous

    tag. - -

    Para1

    Para2 - should be transformed into: -

    Para1

    Para2 - - Some tags can be nested arbitrarily. For instance, the occurance - of a

    tag should _not_ implicitly close the previous -
    tag. - - Alice said:
    Bob said:
    Blah - should NOT be transformed into: - Alice said:
    Bob said:
    Blah - - Some tags can be nested, but the nesting is reset by the - interposition of other tags. For instance, a
    , - but not close a tag in another table. - -
    BlahBlah - should be transformed into: -
    BlahBlah - but, - Blah
    Blah - should NOT be transformed into - Blah
    Blah - - Differing assumptions about tag nesting rules are a major source - of problems with the BeautifulSoup class. If BeautifulSoup is not - treating as nestable a tag your page author treats as nestable, - try ICantBelieveItsBeautifulSoup before writing your own - subclass.""" - - SELF_CLOSING_TAGS = buildTagMap(None, ['br' , 'hr', 'input', 'img', 'meta', - 'spacer', 'link', 'frame', 'base']) - - QUOTE_TAGS = {'script': None} - - #According to the HTML standard, each of these inline tags can - #contain another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_INLINE_TAGS = ['span', 'font', 'q', 'object', 'bdo', 'sub', 'sup', - 'center'] - - #According to the HTML standard, these block tags can contain - #another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_BLOCK_TAGS = ['blockquote', 'div', 'fieldset', 'ins', 'del'] - - #Lists can contain other lists, but there are restrictions. - NESTABLE_LIST_TAGS = { 'ol' : [], - 'ul' : [], - 'li' : ['ul', 'ol'], - 'dl' : [], - 'dd' : ['dl'], - 'dt' : ['dl'] } - - #Tables can contain other tables, but there are restrictions. - NESTABLE_TABLE_TAGS = {'table' : [], - 'tr' : ['table', 'tbody', 'tfoot', 'thead'], - 'td' : ['tr'], - 'th' : ['tr'], - } - - NON_NESTABLE_BLOCK_TAGS = ['address', 'form', 'p', 'pre'] - - #If one of these tags is encountered, all tags up to the next tag of - #this type are popped. - RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript', - NON_NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, - NESTABLE_TABLE_TAGS) - - NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) - -class ICantBelieveItsBeautifulSoup(BeautifulSoup): - - """The BeautifulSoup class is oriented towards skipping over - common HTML errors like unclosed tags. However, sometimes it makes - errors of its own. For instance, consider this fragment: - - FooBar - - This is perfectly valid (if bizarre) HTML. However, the - BeautifulSoup class will implicitly close the first b tag when it - encounters the second 'b'. It will think the author wrote - "FooBar", and didn't close the first 'b' tag, because - there's no real-world reason to bold something that's already - bold. When it encounters '' it will close two more 'b' - tags, for a grand total of three tags closed instead of two. This - can throw off the rest of your document structure. The same is - true of a number of other tags, listed below. - - It's much more common for someone to forget to close (eg.) a 'b' - tag than to actually use nested 'b' tags, and the BeautifulSoup - class handles the common case. This class handles the - not-co-common case: where you can't believe someone wrote what - they did, but it's valid HTML and BeautifulSoup screwed up by - assuming it wouldn't be. - - If this doesn't do what you need, try subclassing this class or - BeautifulSoup, and providing your own list of NESTABLE_TAGS.""" - - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \ - ['em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong', - 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b', - 'big'] - - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ['noscript'] - - NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS) - -class BeautifulSOAP(BeautifulStoneSoup): - """This class will push a tag with only a single string child into - the tag's parent as an attribute. The attribute's name is the tag - name, and the value is the string child. An example should give - the flavor of the change: - - baz - => - baz - - You can then access fooTag['bar'] instead of fooTag.barTag.string. - - This is, of course, useful for scraping structures that tend to - use subelements instead of attributes, such as SOAP messages. Note - that it modifies its input, so don't print the modified version - out. - - I'm not sure how many people really want to use this class; let me - know if you do. Mainly I like the name.""" - - def popTag(self): - if len(self.tagStack) > 1: - tag = self.tagStack[-1] - parent = self.tagStack[-2] - parent._getAttrMap() - if (isinstance(tag, Tag) and len(tag.contents) == 1 and - isinstance(tag.contents[0], NavigableText) and - not parent.attrMap.has_key(tag.name)): - parent[tag.name] = tag.contents[0] - BeautifulStoneSoup.popTag(self) - -#Enterprise class names! It has come to our attention that some people -#think the names of the Beautiful Soup parser classes are too silly -#and "unprofessional" for use in enterprise screen-scraping. We feel -#your pain! For such-minded folk, the Beautiful Soup Consortium And -#All-Night Kosher Bakery recommends renaming this file to -#"RobustParser.py" (or, in cases of extreme enterprisitude, -#"RobustParserBeanInterface.class") and using the following -#enterprise-friendly class aliases: -class RobustXMLParser(BeautifulStoneSoup): - pass -class RobustHTMLParser(BeautifulSoup): - pass -class RobustWackAssHTMLParser(ICantBelieveItsBeautifulSoup): - pass -class SimplifyingSOAPParser(BeautifulSOAP): - pass - -### - - -#By default, act as an HTML pretty-printer. -if __name__ == '__main__': - import sys - soup = BeautifulStoneSoup(sys.stdin.read()) - print soup.prettify() diff -Nru gtkrawgallery-0.9.8/src/mechanize/_clientcookie.py gtkrawgallery-0.9.9/src/mechanize/_clientcookie.py --- gtkrawgallery-0.9.8/src/mechanize/_clientcookie.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/_clientcookie.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1725 +0,0 @@ -"""HTTP cookie handling for web clients. - -This module originally developed from my port of Gisle Aas' Perl module -HTTP::Cookies, from the libwww-perl library. - -Docstrings, comments and debug strings in this code refer to the -attributes of the HTTP cookie system as cookie-attributes, to distinguish -them clearly from Python attributes. - - CookieJar____ - / \ \ - FileCookieJar \ \ - / | \ \ \ - MozillaCookieJar | LWPCookieJar \ \ - | | \ - | ---MSIEBase | \ - | / | | \ - | / MSIEDBCookieJar BSDDBCookieJar - |/ - MSIECookieJar - -Comments to John J Lee . - - -Copyright 2002-2006 John J Lee -Copyright 1997-1999 Gisle Aas (original libwww-perl code) -Copyright 2002-2003 Johnny Lee (original MSIE Perl code) - -This code is free software; you can redistribute it and/or modify it -under the terms of the BSD or ZPL 2.1 licenses (see the file -COPYING.txt included with the distribution). - -""" - -import sys, re, copy, time, urllib, types, logging -try: - import threading - _threading = threading; del threading -except ImportError: - import dummy_threading - _threading = dummy_threading; del dummy_threading - -MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar " - "instance initialised with one)") -DEFAULT_HTTP_PORT = "80" - -from _headersutil import split_header_words, parse_ns_headers -from _util import isstringlike -import _rfc3986 - -debug = logging.getLogger("mechanize.cookies").debug - - -def reraise_unmasked_exceptions(unmasked=()): - # There are a few catch-all except: statements in this module, for - # catching input that's bad in unexpected ways. - # This function re-raises some exceptions we don't want to trap. - import mechanize, warnings - if not mechanize.USE_BARE_EXCEPT: - raise - unmasked = unmasked + (KeyboardInterrupt, SystemExit, MemoryError) - etype = sys.exc_info()[0] - if issubclass(etype, unmasked): - raise - # swallowed an exception - import traceback, StringIO - f = StringIO.StringIO() - traceback.print_exc(None, f) - msg = f.getvalue() - warnings.warn("mechanize bug!\n%s" % msg, stacklevel=2) - - -IPV4_RE = re.compile(r"\.\d+$") -def is_HDN(text): - """Return True if text is a host domain name.""" - # XXX - # This may well be wrong. Which RFC is HDN defined in, if any (for - # the purposes of RFC 2965)? - # For the current implementation, what about IPv6? Remember to look - # at other uses of IPV4_RE also, if change this. - return not (IPV4_RE.search(text) or - text == "" or - text[0] == "." or text[-1] == ".") - -def domain_match(A, B): - """Return True if domain A domain-matches domain B, according to RFC 2965. - - A and B may be host domain names or IP addresses. - - RFC 2965, section 1: - - Host names can be specified either as an IP address or a HDN string. - Sometimes we compare one host name with another. (Such comparisons SHALL - be case-insensitive.) Host A's name domain-matches host B's if - - * their host name strings string-compare equal; or - - * A is a HDN string and has the form NB, where N is a non-empty - name string, B has the form .B', and B' is a HDN string. (So, - x.y.com domain-matches .Y.com but not Y.com.) - - Note that domain-match is not a commutative operation: a.b.c.com - domain-matches .c.com, but not the reverse. - - """ - # Note that, if A or B are IP addresses, the only relevant part of the - # definition of the domain-match algorithm is the direct string-compare. - A = A.lower() - B = B.lower() - if A == B: - return True - if not is_HDN(A): - return False - i = A.rfind(B) - has_form_nb = not (i == -1 or i == 0) - return ( - has_form_nb and - B.startswith(".") and - is_HDN(B[1:]) - ) - -def liberal_is_HDN(text): - """Return True if text is a sort-of-like a host domain name. - - For accepting/blocking domains. - - """ - return not IPV4_RE.search(text) - -def user_domain_match(A, B): - """For blocking/accepting domains. - - A and B may be host domain names or IP addresses. - - """ - A = A.lower() - B = B.lower() - if not (liberal_is_HDN(A) and liberal_is_HDN(B)): - if A == B: - # equal IP addresses - return True - return False - initial_dot = B.startswith(".") - if initial_dot and A.endswith(B): - return True - if not initial_dot and A == B: - return True - return False - -cut_port_re = re.compile(r":\d+$") -def request_host(request): - """Return request-host, as defined by RFC 2965. - - Variation from RFC: returned value is lowercased, for convenient - comparison. - - """ - url = request.get_full_url() - host = _rfc3986.urlsplit(url)[1] - if host is None: - host = request.get_header("Host", "") - # remove port, if present - return cut_port_re.sub("", host, 1) - -def request_host_lc(request): - return request_host(request).lower() - -def eff_request_host(request): - """Return a tuple (request-host, effective request-host name).""" - erhn = req_host = request_host(request) - if req_host.find(".") == -1 and not IPV4_RE.search(req_host): - erhn = req_host + ".local" - return req_host, erhn - -def eff_request_host_lc(request): - req_host, erhn = eff_request_host(request) - return req_host.lower(), erhn.lower() - -def effective_request_host(request): - """Return the effective request-host, as defined by RFC 2965.""" - return eff_request_host(request)[1] - -def request_path(request): - """Return path component of request-URI, as defined by RFC 2965.""" - url = request.get_full_url() - path = escape_path(_rfc3986.urlsplit(url)[2]) - if not path.startswith("/"): - path = "/" + path - return path - -def request_port(request): - host = request.get_host() - i = host.find(':') - if i >= 0: - port = host[i+1:] - try: - int(port) - except ValueError: - debug("nonnumeric port: '%s'", port) - return None - else: - port = DEFAULT_HTTP_PORT - return port - -def request_is_unverifiable(request): - try: - return request.is_unverifiable() - except AttributeError: - if hasattr(request, "unverifiable"): - return request.unverifiable - else: - raise - -# Characters in addition to A-Z, a-z, 0-9, '_', '.', and '-' that don't -# need to be escaped to form a valid HTTP URL (RFCs 2396 and 1738). -HTTP_PATH_SAFE = "%/;:@&=+$,!~*'()" -ESCAPED_CHAR_RE = re.compile(r"%([0-9a-fA-F][0-9a-fA-F])") -def uppercase_escaped_char(match): - return "%%%s" % match.group(1).upper() -def escape_path(path): - """Escape any invalid characters in HTTP URL, and uppercase all escapes.""" - # There's no knowing what character encoding was used to create URLs - # containing %-escapes, but since we have to pick one to escape invalid - # path characters, we pick UTF-8, as recommended in the HTML 4.0 - # specification: - # http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1 - # And here, kind of: draft-fielding-uri-rfc2396bis-03 - # (And in draft IRI specification: draft-duerst-iri-05) - # (And here, for new URI schemes: RFC 2718) - if isinstance(path, types.UnicodeType): - path = path.encode("utf-8") - path = urllib.quote(path, HTTP_PATH_SAFE) - path = ESCAPED_CHAR_RE.sub(uppercase_escaped_char, path) - return path - -def reach(h): - """Return reach of host h, as defined by RFC 2965, section 1. - - The reach R of a host name H is defined as follows: - - * If - - - H is the host domain name of a host; and, - - - H has the form A.B; and - - - A has no embedded (that is, interior) dots; and - - - B has at least one embedded dot, or B is the string "local". - then the reach of H is .B. - - * Otherwise, the reach of H is H. - - >>> reach("www.acme.com") - '.acme.com' - >>> reach("acme.com") - 'acme.com' - >>> reach("acme.local") - '.local' - - """ - i = h.find(".") - if i >= 0: - #a = h[:i] # this line is only here to show what a is - b = h[i+1:] - i = b.find(".") - if is_HDN(h) and (i >= 0 or b == "local"): - return "."+b - return h - -def is_third_party(request): - """ - - RFC 2965, section 3.3.6: - - An unverifiable transaction is to a third-party host if its request- - host U does not domain-match the reach R of the request-host O in the - origin transaction. - - """ - req_host = request_host_lc(request) - # the origin request's request-host was stuffed into request by - # _urllib2_support.AbstractHTTPHandler - return not domain_match(req_host, reach(request.origin_req_host)) - - -try: - all -except NameError: - # python 2.4 - def all(iterable): - for x in iterable: - if not x: - return False - return True - - -class Cookie: - """HTTP Cookie. - - This class represents both Netscape and RFC 2965 cookies. - - This is deliberately a very simple class. It just holds attributes. It's - possible to construct Cookie instances that don't comply with the cookie - standards. CookieJar.make_cookies is the factory function for Cookie - objects -- it deals with cookie parsing, supplying defaults, and - normalising to the representation used in this class. CookiePolicy is - responsible for checking them to see whether they should be accepted from - and returned to the server. - - version: integer; - name: string; - value: string (may be None); - port: string; None indicates no attribute was supplied (e.g. "Port", rather - than eg. "Port=80"); otherwise, a port string (eg. "80") or a port list - string (e.g. "80,8080") - port_specified: boolean; true if a value was supplied with the Port - cookie-attribute - domain: string; - domain_specified: boolean; true if Domain was explicitly set - domain_initial_dot: boolean; true if Domain as set in HTTP header by server - started with a dot (yes, this really is necessary!) - path: string; - path_specified: boolean; true if Path was explicitly set - secure: boolean; true if should only be returned over secure connection - expires: integer; seconds since epoch (RFC 2965 cookies should calculate - this value from the Max-Age attribute) - discard: boolean, true if this is a session cookie; (if no expires value, - this should be true) - comment: string; - comment_url: string; - rfc2109: boolean; true if cookie arrived in a Set-Cookie: (not - Set-Cookie2:) header, but had a version cookie-attribute of 1 - rest: mapping of other cookie-attributes - - Note that the port may be present in the headers, but unspecified ("Port" - rather than"Port=80", for example); if this is the case, port is None. - - """ - - - _attrs = ("version", "name", "value", - "port", "port_specified", - "domain", "domain_specified", "domain_initial_dot", - "path", "path_specified", - "secure", "expires", "discard", "comment", "comment_url", - "rfc2109", "_rest") - - def __init__(self, version, name, value, - port, port_specified, - domain, domain_specified, domain_initial_dot, - path, path_specified, - secure, - expires, - discard, - comment, - comment_url, - rest, - rfc2109=False, - ): - - if version is not None: version = int(version) - if expires is not None: expires = int(expires) - if port is None and port_specified is True: - raise ValueError("if port is None, port_specified must be false") - - self.version = version - self.name = name - self.value = value - self.port = port - self.port_specified = port_specified - # normalise case, as per RFC 2965 section 3.3.3 - self.domain = domain.lower() - self.domain_specified = domain_specified - # Sigh. We need to know whether the domain given in the - # cookie-attribute had an initial dot, in order to follow RFC 2965 - # (as clarified in draft errata). Needed for the returned $Domain - # value. - self.domain_initial_dot = domain_initial_dot - self.path = path - self.path_specified = path_specified - self.secure = secure - self.expires = expires - self.discard = discard - self.comment = comment - self.comment_url = comment_url - self.rfc2109 = rfc2109 - - self._rest = copy.copy(rest) - - def has_nonstandard_attr(self, name): - return self._rest.has_key(name) - def get_nonstandard_attr(self, name, default=None): - return self._rest.get(name, default) - def set_nonstandard_attr(self, name, value): - self._rest[name] = value - def nonstandard_attr_keys(self): - return self._rest.keys() - - def is_expired(self, now=None): - if now is None: now = time.time() - return (self.expires is not None) and (self.expires <= now) - - def __eq__(self, other): - return all(getattr(self, a) == getattr(other, a) for a in self._attrs) - - def __ne__(self, other): - return not (self == other) - - def __str__(self): - if self.port is None: p = "" - else: p = ":"+self.port - limit = self.domain + p + self.path - if self.value is not None: - namevalue = "%s=%s" % (self.name, self.value) - else: - namevalue = self.name - return "" % (namevalue, limit) - - def __repr__(self): - args = [] - for name in ["version", "name", "value", - "port", "port_specified", - "domain", "domain_specified", "domain_initial_dot", - "path", "path_specified", - "secure", "expires", "discard", "comment", "comment_url", - ]: - attr = getattr(self, name) - args.append("%s=%s" % (name, repr(attr))) - args.append("rest=%s" % repr(self._rest)) - args.append("rfc2109=%s" % repr(self.rfc2109)) - return "Cookie(%s)" % ", ".join(args) - - -class CookiePolicy: - """Defines which cookies get accepted from and returned to server. - - May also modify cookies. - - The subclass DefaultCookiePolicy defines the standard rules for Netscape - and RFC 2965 cookies -- override that if you want a customised policy. - - As well as implementing set_ok and return_ok, implementations of this - interface must also supply the following attributes, indicating which - protocols should be used, and how. These can be read and set at any time, - though whether that makes complete sense from the protocol point of view is - doubtful. - - Public attributes: - - netscape: implement netscape protocol - rfc2965: implement RFC 2965 protocol - rfc2109_as_netscape: - WARNING: This argument will change or go away if is not accepted into - the Python standard library in this form! - If true, treat RFC 2109 cookies as though they were Netscape cookies. The - default is for this attribute to be None, which means treat 2109 cookies - as RFC 2965 cookies unless RFC 2965 handling is switched off (which it is, - by default), and as Netscape cookies otherwise. - hide_cookie2: don't add Cookie2 header to requests (the presence of - this header indicates to the server that we understand RFC 2965 - cookies) - - """ - def set_ok(self, cookie, request): - """Return true if (and only if) cookie should be accepted from server. - - Currently, pre-expired cookies never get this far -- the CookieJar - class deletes such cookies itself. - - cookie: mechanize.Cookie object - request: object implementing the interface defined by - CookieJar.extract_cookies.__doc__ - - """ - raise NotImplementedError() - - def return_ok(self, cookie, request): - """Return true if (and only if) cookie should be returned to server. - - cookie: mechanize.Cookie object - request: object implementing the interface defined by - CookieJar.add_cookie_header.__doc__ - - """ - raise NotImplementedError() - - def domain_return_ok(self, domain, request): - """Return false if cookies should not be returned, given cookie domain. - - This is here as an optimization, to remove the need for checking every - cookie with a particular domain (which may involve reading many files). - The default implementations of domain_return_ok and path_return_ok - (return True) leave all the work to return_ok. - - If domain_return_ok returns true for the cookie domain, path_return_ok - is called for the cookie path. Otherwise, path_return_ok and return_ok - are never called for that cookie domain. If path_return_ok returns - true, return_ok is called with the Cookie object itself for a full - check. Otherwise, return_ok is never called for that cookie path. - - Note that domain_return_ok is called for every *cookie* domain, not - just for the *request* domain. For example, the function might be - called with both ".acme.com" and "www.acme.com" if the request domain - is "www.acme.com". The same goes for path_return_ok. - - For argument documentation, see the docstring for return_ok. - - """ - return True - - def path_return_ok(self, path, request): - """Return false if cookies should not be returned, given cookie path. - - See the docstring for domain_return_ok. - - """ - return True - - -class DefaultCookiePolicy(CookiePolicy): - """Implements the standard rules for accepting and returning cookies. - - Both RFC 2965 and Netscape cookies are covered. RFC 2965 handling is - switched off by default. - - The easiest way to provide your own policy is to override this class and - call its methods in your overriden implementations before adding your own - additional checks. - - import mechanize - class MyCookiePolicy(mechanize.DefaultCookiePolicy): - def set_ok(self, cookie, request): - if not mechanize.DefaultCookiePolicy.set_ok( - self, cookie, request): - return False - if i_dont_want_to_store_this_cookie(): - return False - return True - - In addition to the features required to implement the CookiePolicy - interface, this class allows you to block and allow domains from setting - and receiving cookies. There are also some strictness switches that allow - you to tighten up the rather loose Netscape protocol rules a little bit (at - the cost of blocking some benign cookies). - - A domain blacklist and whitelist is provided (both off by default). Only - domains not in the blacklist and present in the whitelist (if the whitelist - is active) participate in cookie setting and returning. Use the - blocked_domains constructor argument, and blocked_domains and - set_blocked_domains methods (and the corresponding argument and methods for - allowed_domains). If you set a whitelist, you can turn it off again by - setting it to None. - - Domains in block or allow lists that do not start with a dot must - string-compare equal. For example, "acme.com" matches a blacklist entry of - "acme.com", but "www.acme.com" does not. Domains that do start with a dot - are matched by more specific domains too. For example, both "www.acme.com" - and "www.munitions.acme.com" match ".acme.com" (but "acme.com" itself does - not). IP addresses are an exception, and must match exactly. For example, - if blocked_domains contains "192.168.1.2" and ".168.1.2" 192.168.1.2 is - blocked, but 193.168.1.2 is not. - - Additional Public Attributes: - - General strictness switches - - strict_domain: don't allow sites to set two-component domains with - country-code top-level domains like .co.uk, .gov.uk, .co.nz. etc. - This is far from perfect and isn't guaranteed to work! - - RFC 2965 protocol strictness switches - - strict_rfc2965_unverifiable: follow RFC 2965 rules on unverifiable - transactions (usually, an unverifiable transaction is one resulting from - a redirect or an image hosted on another site); if this is false, cookies - are NEVER blocked on the basis of verifiability - - Netscape protocol strictness switches - - strict_ns_unverifiable: apply RFC 2965 rules on unverifiable transactions - even to Netscape cookies - strict_ns_domain: flags indicating how strict to be with domain-matching - rules for Netscape cookies: - DomainStrictNoDots: when setting cookies, host prefix must not contain a - dot (e.g. www.foo.bar.com can't set a cookie for .bar.com, because - www.foo contains a dot) - DomainStrictNonDomain: cookies that did not explicitly specify a Domain - cookie-attribute can only be returned to a domain that string-compares - equal to the domain that set the cookie (e.g. rockets.acme.com won't - be returned cookies from acme.com that had no Domain cookie-attribute) - DomainRFC2965Match: when setting cookies, require a full RFC 2965 - domain-match - DomainLiberal and DomainStrict are the most useful combinations of the - above flags, for convenience - strict_ns_set_initial_dollar: ignore cookies in Set-Cookie: headers that - have names starting with '$' - strict_ns_set_path: don't allow setting cookies whose path doesn't - path-match request URI - - """ - - DomainStrictNoDots = 1 - DomainStrictNonDomain = 2 - DomainRFC2965Match = 4 - - DomainLiberal = 0 - DomainStrict = DomainStrictNoDots|DomainStrictNonDomain - - def __init__(self, - blocked_domains=None, allowed_domains=None, - netscape=True, rfc2965=False, - # WARNING: this argument will change or go away if is not - # accepted into the Python standard library in this form! - # default, ie. treat 2109 as netscape iff not rfc2965 - rfc2109_as_netscape=None, - hide_cookie2=False, - strict_domain=False, - strict_rfc2965_unverifiable=True, - strict_ns_unverifiable=False, - strict_ns_domain=DomainLiberal, - strict_ns_set_initial_dollar=False, - strict_ns_set_path=False, - ): - """ - Constructor arguments should be used as keyword arguments only. - - blocked_domains: sequence of domain names that we never accept cookies - from, nor return cookies to - allowed_domains: if not None, this is a sequence of the only domains - for which we accept and return cookies - - For other arguments, see CookiePolicy.__doc__ and - DefaultCookiePolicy.__doc__.. - - """ - self.netscape = netscape - self.rfc2965 = rfc2965 - self.rfc2109_as_netscape = rfc2109_as_netscape - self.hide_cookie2 = hide_cookie2 - self.strict_domain = strict_domain - self.strict_rfc2965_unverifiable = strict_rfc2965_unverifiable - self.strict_ns_unverifiable = strict_ns_unverifiable - self.strict_ns_domain = strict_ns_domain - self.strict_ns_set_initial_dollar = strict_ns_set_initial_dollar - self.strict_ns_set_path = strict_ns_set_path - - if blocked_domains is not None: - self._blocked_domains = tuple(blocked_domains) - else: - self._blocked_domains = () - - if allowed_domains is not None: - allowed_domains = tuple(allowed_domains) - self._allowed_domains = allowed_domains - - def blocked_domains(self): - """Return the sequence of blocked domains (as a tuple).""" - return self._blocked_domains - def set_blocked_domains(self, blocked_domains): - """Set the sequence of blocked domains.""" - self._blocked_domains = tuple(blocked_domains) - - def is_blocked(self, domain): - for blocked_domain in self._blocked_domains: - if user_domain_match(domain, blocked_domain): - return True - return False - - def allowed_domains(self): - """Return None, or the sequence of allowed domains (as a tuple).""" - return self._allowed_domains - def set_allowed_domains(self, allowed_domains): - """Set the sequence of allowed domains, or None.""" - if allowed_domains is not None: - allowed_domains = tuple(allowed_domains) - self._allowed_domains = allowed_domains - - def is_not_allowed(self, domain): - if self._allowed_domains is None: - return False - for allowed_domain in self._allowed_domains: - if user_domain_match(domain, allowed_domain): - return False - return True - - def set_ok(self, cookie, request): - """ - If you override set_ok, be sure to call this method. If it returns - false, so should your subclass (assuming your subclass wants to be more - strict about which cookies to accept). - - """ - debug(" - checking cookie %s", cookie) - - assert cookie.name is not None - - for n in "version", "verifiability", "name", "path", "domain", "port": - fn_name = "set_ok_"+n - fn = getattr(self, fn_name) - if not fn(cookie, request): - return False - - return True - - def set_ok_version(self, cookie, request): - if cookie.version is None: - # Version is always set to 0 by parse_ns_headers if it's a Netscape - # cookie, so this must be an invalid RFC 2965 cookie. - debug(" Set-Cookie2 without version attribute (%s)", cookie) - return False - if cookie.version > 0 and not self.rfc2965: - debug(" RFC 2965 cookies are switched off") - return False - elif cookie.version == 0 and not self.netscape: - debug(" Netscape cookies are switched off") - return False - return True - - def set_ok_verifiability(self, cookie, request): - if request_is_unverifiable(request) and is_third_party(request): - if cookie.version > 0 and self.strict_rfc2965_unverifiable: - debug(" third-party RFC 2965 cookie during " - "unverifiable transaction") - return False - elif cookie.version == 0 and self.strict_ns_unverifiable: - debug(" third-party Netscape cookie during " - "unverifiable transaction") - return False - return True - - def set_ok_name(self, cookie, request): - # Try and stop servers setting V0 cookies designed to hack other - # servers that know both V0 and V1 protocols. - if (cookie.version == 0 and self.strict_ns_set_initial_dollar and - cookie.name.startswith("$")): - debug(" illegal name (starts with '$'): '%s'", cookie.name) - return False - return True - - def set_ok_path(self, cookie, request): - if cookie.path_specified: - req_path = request_path(request) - if ((cookie.version > 0 or - (cookie.version == 0 and self.strict_ns_set_path)) and - not req_path.startswith(cookie.path)): - debug(" path attribute %s is not a prefix of request " - "path %s", cookie.path, req_path) - return False - return True - - def set_ok_countrycode_domain(self, cookie, request): - """Return False if explicit cookie domain is not acceptable. - - Called by set_ok_domain, for convenience of overriding by - subclasses. - - """ - if cookie.domain_specified and self.strict_domain: - domain = cookie.domain - # since domain was specified, we know that: - assert domain.startswith(".") - if domain.count(".") == 2: - # domain like .foo.bar - i = domain.rfind(".") - tld = domain[i+1:] - sld = domain[1:i] - if (sld.lower() in [ - "co", "ac", - "com", "edu", "org", "net", "gov", "mil", "int", - "aero", "biz", "cat", "coop", "info", "jobs", "mobi", - "museum", "name", "pro", "travel", - ] and - len(tld) == 2): - # domain like .co.uk - return False - return True - - def set_ok_domain(self, cookie, request): - if self.is_blocked(cookie.domain): - debug(" domain %s is in user block-list", cookie.domain) - return False - if self.is_not_allowed(cookie.domain): - debug(" domain %s is not in user allow-list", cookie.domain) - return False - if not self.set_ok_countrycode_domain(cookie, request): - debug(" country-code second level domain %s", cookie.domain) - return False - if cookie.domain_specified: - req_host, erhn = eff_request_host_lc(request) - domain = cookie.domain - if domain.startswith("."): - undotted_domain = domain[1:] - else: - undotted_domain = domain - embedded_dots = (undotted_domain.find(".") >= 0) - if not embedded_dots and domain != ".local": - debug(" non-local domain %s contains no embedded dot", - domain) - return False - if cookie.version == 0: - if (not erhn.endswith(domain) and - (not erhn.startswith(".") and - not ("."+erhn).endswith(domain))): - debug(" effective request-host %s (even with added " - "initial dot) does not end end with %s", - erhn, domain) - return False - if (cookie.version > 0 or - (self.strict_ns_domain & self.DomainRFC2965Match)): - if not domain_match(erhn, domain): - debug(" effective request-host %s does not domain-match " - "%s", erhn, domain) - return False - if (cookie.version > 0 or - (self.strict_ns_domain & self.DomainStrictNoDots)): - host_prefix = req_host[:-len(domain)] - if (host_prefix.find(".") >= 0 and - not IPV4_RE.search(req_host)): - debug(" host prefix %s for domain %s contains a dot", - host_prefix, domain) - return False - return True - - def set_ok_port(self, cookie, request): - if cookie.port_specified: - req_port = request_port(request) - if req_port is None: - req_port = "80" - else: - req_port = str(req_port) - for p in cookie.port.split(","): - try: - int(p) - except ValueError: - debug(" bad port %s (not numeric)", p) - return False - if p == req_port: - break - else: - debug(" request port (%s) not found in %s", - req_port, cookie.port) - return False - return True - - def return_ok(self, cookie, request): - """ - If you override return_ok, be sure to call this method. If it returns - false, so should your subclass (assuming your subclass wants to be more - strict about which cookies to return). - - """ - # Path has already been checked by path_return_ok, and domain blocking - # done by domain_return_ok. - debug(" - checking cookie %s", cookie) - - for n in ("version", "verifiability", "secure", "expires", "port", - "domain"): - fn_name = "return_ok_"+n - fn = getattr(self, fn_name) - if not fn(cookie, request): - return False - return True - - def return_ok_version(self, cookie, request): - if cookie.version > 0 and not self.rfc2965: - debug(" RFC 2965 cookies are switched off") - return False - elif cookie.version == 0 and not self.netscape: - debug(" Netscape cookies are switched off") - return False - return True - - def return_ok_verifiability(self, cookie, request): - if request_is_unverifiable(request) and is_third_party(request): - if cookie.version > 0 and self.strict_rfc2965_unverifiable: - debug(" third-party RFC 2965 cookie during unverifiable " - "transaction") - return False - elif cookie.version == 0 and self.strict_ns_unverifiable: - debug(" third-party Netscape cookie during unverifiable " - "transaction") - return False - return True - - def return_ok_secure(self, cookie, request): - if cookie.secure and request.get_type() != "https": - debug(" secure cookie with non-secure request") - return False - return True - - def return_ok_expires(self, cookie, request): - if cookie.is_expired(self._now): - debug(" cookie expired") - return False - return True - - def return_ok_port(self, cookie, request): - if cookie.port: - req_port = request_port(request) - if req_port is None: - req_port = "80" - for p in cookie.port.split(","): - if p == req_port: - break - else: - debug(" request port %s does not match cookie port %s", - req_port, cookie.port) - return False - return True - - def return_ok_domain(self, cookie, request): - req_host, erhn = eff_request_host_lc(request) - domain = cookie.domain - - # strict check of non-domain cookies: Mozilla does this, MSIE5 doesn't - if (cookie.version == 0 and - (self.strict_ns_domain & self.DomainStrictNonDomain) and - not cookie.domain_specified and domain != erhn): - debug(" cookie with unspecified domain does not string-compare " - "equal to request domain") - return False - - if cookie.version > 0 and not domain_match(erhn, domain): - debug(" effective request-host name %s does not domain-match " - "RFC 2965 cookie domain %s", erhn, domain) - return False - if cookie.version == 0 and not ("."+erhn).endswith(domain): - debug(" request-host %s does not match Netscape cookie domain " - "%s", req_host, domain) - return False - return True - - def domain_return_ok(self, domain, request): - # Liberal check of domain. This is here as an optimization to avoid - # having to load lots of MSIE cookie files unless necessary. - - # Munge req_host and erhn to always start with a dot, so as to err on - # the side of letting cookies through. - dotted_req_host, dotted_erhn = eff_request_host_lc(request) - if not dotted_req_host.startswith("."): - dotted_req_host = "."+dotted_req_host - if not dotted_erhn.startswith("."): - dotted_erhn = "."+dotted_erhn - if not (dotted_req_host.endswith(domain) or - dotted_erhn.endswith(domain)): - #debug(" request domain %s does not match cookie domain %s", - # req_host, domain) - return False - - if self.is_blocked(domain): - debug(" domain %s is in user block-list", domain) - return False - if self.is_not_allowed(domain): - debug(" domain %s is not in user allow-list", domain) - return False - - return True - - def path_return_ok(self, path, request): - debug("- checking cookie path=%s", path) - req_path = request_path(request) - if not req_path.startswith(path): - debug(" %s does not path-match %s", req_path, path) - return False - return True - - -def vals_sorted_by_key(adict): - keys = adict.keys() - keys.sort() - return map(adict.get, keys) - -class MappingIterator: - """Iterates over nested mapping, depth-first, in sorted order by key.""" - def __init__(self, mapping): - self._s = [(vals_sorted_by_key(mapping), 0, None)] # LIFO stack - - def __iter__(self): return self - - def next(self): - # this is hairy because of lack of generators - while 1: - try: - vals, i, prev_item = self._s.pop() - except IndexError: - raise StopIteration() - if i < len(vals): - item = vals[i] - i = i + 1 - self._s.append((vals, i, prev_item)) - try: - item.items - except AttributeError: - # non-mapping - break - else: - # mapping - self._s.append((vals_sorted_by_key(item), 0, item)) - continue - return item - - -# Used as second parameter to dict.get method, to distinguish absent -# dict key from one with a None value. -class Absent: pass - -class CookieJar: - """Collection of HTTP cookies. - - You may not need to know about this class: try mechanize.urlopen(). - - The major methods are extract_cookies and add_cookie_header; these are all - you are likely to need. - - CookieJar supports the iterator protocol: - - for cookie in cookiejar: - # do something with cookie - - Methods: - - add_cookie_header(request) - extract_cookies(response, request) - get_policy() - set_policy(policy) - cookies_for_request(request) - make_cookies(response, request) - set_cookie_if_ok(cookie, request) - set_cookie(cookie) - clear_session_cookies() - clear_expired_cookies() - clear(domain=None, path=None, name=None) - - Public attributes - - policy: CookiePolicy object - - """ - - non_word_re = re.compile(r"\W") - quote_re = re.compile(r"([\"\\])") - strict_domain_re = re.compile(r"\.?[^.]*") - domain_re = re.compile(r"[^.]*") - dots_re = re.compile(r"^\.+") - - def __init__(self, policy=None): - """ - See CookieJar.__doc__ for argument documentation. - - """ - if policy is None: - policy = DefaultCookiePolicy() - self._policy = policy - - self._cookies = {} - - # for __getitem__ iteration in pre-2.2 Pythons - self._prev_getitem_index = 0 - - def get_policy(self): - return self._policy - - def set_policy(self, policy): - self._policy = policy - - def _cookies_for_domain(self, domain, request): - cookies = [] - if not self._policy.domain_return_ok(domain, request): - return [] - debug("Checking %s for cookies to return", domain) - cookies_by_path = self._cookies[domain] - for path in cookies_by_path.keys(): - if not self._policy.path_return_ok(path, request): - continue - cookies_by_name = cookies_by_path[path] - for cookie in cookies_by_name.values(): - if not self._policy.return_ok(cookie, request): - debug(" not returning cookie") - continue - debug(" it's a match") - cookies.append(cookie) - return cookies - - def cookies_for_request(self, request): - """Return a list of cookies to be returned to server. - - The returned list of cookie instances is sorted in the order they - should appear in the Cookie: header for return to the server. - - See add_cookie_header.__doc__ for the interface required of the - request argument. - - New in version 0.1.10 - - """ - self._policy._now = self._now = int(time.time()) - cookies = self._cookies_for_request(request) - # add cookies in order of most specific (i.e. longest) path first - def decreasing_size(a, b): return cmp(len(b.path), len(a.path)) - cookies.sort(decreasing_size) - return cookies - - def _cookies_for_request(self, request): - """Return a list of cookies to be returned to server.""" - # this method still exists (alongside cookies_for_request) because it - # is part of an implied protected interface for subclasses of cookiejar - # XXX document that implied interface, or provide another way of - # implementing cookiejars than subclassing - cookies = [] - for domain in self._cookies.keys(): - cookies.extend(self._cookies_for_domain(domain, request)) - return cookies - - def _cookie_attrs(self, cookies): - """Return a list of cookie-attributes to be returned to server. - - The $Version attribute is also added when appropriate (currently only - once per request). - - >>> jar = CookieJar() - >>> ns_cookie = Cookie(0, "foo", '"bar"', None, False, - ... "example.com", False, False, - ... "/", False, False, None, True, - ... None, None, {}) - >>> jar._cookie_attrs([ns_cookie]) - ['foo="bar"'] - >>> rfc2965_cookie = Cookie(1, "foo", "bar", None, False, - ... ".example.com", True, False, - ... "/", False, False, None, True, - ... None, None, {}) - >>> jar._cookie_attrs([rfc2965_cookie]) - ['$Version=1', 'foo=bar', '$Domain="example.com"'] - - """ - version_set = False - - attrs = [] - for cookie in cookies: - # set version of Cookie header - # XXX - # What should it be if multiple matching Set-Cookie headers have - # different versions themselves? - # Answer: there is no answer; was supposed to be settled by - # RFC 2965 errata, but that may never appear... - version = cookie.version - if not version_set: - version_set = True - if version > 0: - attrs.append("$Version=%s" % version) - - # quote cookie value if necessary - # (not for Netscape protocol, which already has any quotes - # intact, due to the poorly-specified Netscape Cookie: syntax) - if ((cookie.value is not None) and - self.non_word_re.search(cookie.value) and version > 0): - value = self.quote_re.sub(r"\\\1", cookie.value) - else: - value = cookie.value - - # add cookie-attributes to be returned in Cookie header - if cookie.value is None: - attrs.append(cookie.name) - else: - attrs.append("%s=%s" % (cookie.name, value)) - if version > 0: - if cookie.path_specified: - attrs.append('$Path="%s"' % cookie.path) - if cookie.domain.startswith("."): - domain = cookie.domain - if (not cookie.domain_initial_dot and - domain.startswith(".")): - domain = domain[1:] - attrs.append('$Domain="%s"' % domain) - if cookie.port is not None: - p = "$Port" - if cookie.port_specified: - p = p + ('="%s"' % cookie.port) - attrs.append(p) - - return attrs - - def add_cookie_header(self, request): - """Add correct Cookie: header to request (mechanize.Request object). - - The Cookie2 header is also added unless policy.hide_cookie2 is true. - - The request object (usually a mechanize.Request instance) must support - the methods get_full_url, get_host, is_unverifiable, get_type, - has_header, get_header, header_items and add_unredirected_header, as - documented by urllib2. - """ - debug("add_cookie_header") - cookies = self.cookies_for_request(request) - - attrs = self._cookie_attrs(cookies) - if attrs: - if not request.has_header("Cookie"): - request.add_unredirected_header("Cookie", "; ".join(attrs)) - - # if necessary, advertise that we know RFC 2965 - if self._policy.rfc2965 and not self._policy.hide_cookie2: - for cookie in cookies: - if cookie.version != 1 and not request.has_header("Cookie2"): - request.add_unredirected_header("Cookie2", '$Version="1"') - break - - self.clear_expired_cookies() - - def _normalized_cookie_tuples(self, attrs_set): - """Return list of tuples containing normalised cookie information. - - attrs_set is the list of lists of key,value pairs extracted from - the Set-Cookie or Set-Cookie2 headers. - - Tuples are name, value, standard, rest, where name and value are the - cookie name and value, standard is a dictionary containing the standard - cookie-attributes (discard, secure, version, expires or max-age, - domain, path and port) and rest is a dictionary containing the rest of - the cookie-attributes. - - """ - cookie_tuples = [] - - boolean_attrs = "discard", "secure" - value_attrs = ("version", - "expires", "max-age", - "domain", "path", "port", - "comment", "commenturl") - - for cookie_attrs in attrs_set: - name, value = cookie_attrs[0] - - # Build dictionary of standard cookie-attributes (standard) and - # dictionary of other cookie-attributes (rest). - - # Note: expiry time is normalised to seconds since epoch. V0 - # cookies should have the Expires cookie-attribute, and V1 cookies - # should have Max-Age, but since V1 includes RFC 2109 cookies (and - # since V0 cookies may be a mish-mash of Netscape and RFC 2109), we - # accept either (but prefer Max-Age). - max_age_set = False - - bad_cookie = False - - standard = {} - rest = {} - for k, v in cookie_attrs[1:]: - lc = k.lower() - # don't lose case distinction for unknown fields - if lc in value_attrs or lc in boolean_attrs: - k = lc - if k in boolean_attrs and v is None: - # boolean cookie-attribute is present, but has no value - # (like "discard", rather than "port=80") - v = True - if standard.has_key(k): - # only first value is significant - continue - if k == "domain": - if v is None: - debug(" missing value for domain attribute") - bad_cookie = True - break - # RFC 2965 section 3.3.3 - v = v.lower() - if k == "expires": - if max_age_set: - # Prefer max-age to expires (like Mozilla) - continue - if v is None: - debug(" missing or invalid value for expires " - "attribute: treating as session cookie") - continue - if k == "max-age": - max_age_set = True - if v is None: - debug(" missing value for max-age attribute") - bad_cookie = True - break - try: - v = int(v) - except ValueError: - debug(" missing or invalid (non-numeric) value for " - "max-age attribute") - bad_cookie = True - break - # convert RFC 2965 Max-Age to seconds since epoch - # XXX Strictly you're supposed to follow RFC 2616 - # age-calculation rules. Remember that zero Max-Age is a - # is a request to discard (old and new) cookie, though. - k = "expires" - v = self._now + v - if (k in value_attrs) or (k in boolean_attrs): - if (v is None and - k not in ["port", "comment", "commenturl"]): - debug(" missing value for %s attribute" % k) - bad_cookie = True - break - standard[k] = v - else: - rest[k] = v - - if bad_cookie: - continue - - cookie_tuples.append((name, value, standard, rest)) - - return cookie_tuples - - def _cookie_from_cookie_tuple(self, tup, request): - # standard is dict of standard cookie-attributes, rest is dict of the - # rest of them - name, value, standard, rest = tup - - domain = standard.get("domain", Absent) - path = standard.get("path", Absent) - port = standard.get("port", Absent) - expires = standard.get("expires", Absent) - - # set the easy defaults - version = standard.get("version", None) - if version is not None: - try: - version = int(version) - except ValueError: - return None # invalid version, ignore cookie - secure = standard.get("secure", False) - # (discard is also set if expires is Absent) - discard = standard.get("discard", False) - comment = standard.get("comment", None) - comment_url = standard.get("commenturl", None) - - # set default path - if path is not Absent and path != "": - path_specified = True - path = escape_path(path) - else: - path_specified = False - path = request_path(request) - i = path.rfind("/") - if i != -1: - if version == 0: - # Netscape spec parts company from reality here - path = path[:i] - else: - path = path[:i+1] - if len(path) == 0: path = "/" - - # set default domain - domain_specified = domain is not Absent - # but first we have to remember whether it starts with a dot - domain_initial_dot = False - if domain_specified: - domain_initial_dot = bool(domain.startswith(".")) - if domain is Absent: - req_host, erhn = eff_request_host_lc(request) - domain = erhn - elif not domain.startswith("."): - domain = "."+domain - - # set default port - port_specified = False - if port is not Absent: - if port is None: - # Port attr present, but has no value: default to request port. - # Cookie should then only be sent back on that port. - port = request_port(request) - else: - port_specified = True - port = re.sub(r"\s+", "", port) - else: - # No port attr present. Cookie can be sent back on any port. - port = None - - # set default expires and discard - if expires is Absent: - expires = None - discard = True - - return Cookie(version, - name, value, - port, port_specified, - domain, domain_specified, domain_initial_dot, - path, path_specified, - secure, - expires, - discard, - comment, - comment_url, - rest) - - def _cookies_from_attrs_set(self, attrs_set, request): - cookie_tuples = self._normalized_cookie_tuples(attrs_set) - - cookies = [] - for tup in cookie_tuples: - cookie = self._cookie_from_cookie_tuple(tup, request) - if cookie: cookies.append(cookie) - return cookies - - def _process_rfc2109_cookies(self, cookies): - if self._policy.rfc2109_as_netscape is None: - rfc2109_as_netscape = not self._policy.rfc2965 - else: - rfc2109_as_netscape = self._policy.rfc2109_as_netscape - for cookie in cookies: - if cookie.version == 1: - cookie.rfc2109 = True - if rfc2109_as_netscape: - # treat 2109 cookies as Netscape cookies rather than - # as RFC2965 cookies - cookie.version = 0 - - def _make_cookies(self, response, request): - # get cookie-attributes for RFC 2965 and Netscape protocols - headers = response.info() - rfc2965_hdrs = headers.getheaders("Set-Cookie2") - ns_hdrs = headers.getheaders("Set-Cookie") - - rfc2965 = self._policy.rfc2965 - netscape = self._policy.netscape - - if ((not rfc2965_hdrs and not ns_hdrs) or - (not ns_hdrs and not rfc2965) or - (not rfc2965_hdrs and not netscape) or - (not netscape and not rfc2965)): - return [] # no relevant cookie headers: quick exit - - try: - cookies = self._cookies_from_attrs_set( - split_header_words(rfc2965_hdrs), request) - except: - reraise_unmasked_exceptions() - cookies = [] - - if ns_hdrs and netscape: - try: - # RFC 2109 and Netscape cookies - ns_cookies = self._cookies_from_attrs_set( - parse_ns_headers(ns_hdrs), request) - except: - reraise_unmasked_exceptions() - ns_cookies = [] - self._process_rfc2109_cookies(ns_cookies) - - # Look for Netscape cookies (from Set-Cookie headers) that match - # corresponding RFC 2965 cookies (from Set-Cookie2 headers). - # For each match, keep the RFC 2965 cookie and ignore the Netscape - # cookie (RFC 2965 section 9.1). Actually, RFC 2109 cookies are - # bundled in with the Netscape cookies for this purpose, which is - # reasonable behaviour. - if rfc2965: - lookup = {} - for cookie in cookies: - lookup[(cookie.domain, cookie.path, cookie.name)] = None - - def no_matching_rfc2965(ns_cookie, lookup=lookup): - key = ns_cookie.domain, ns_cookie.path, ns_cookie.name - return not lookup.has_key(key) - ns_cookies = filter(no_matching_rfc2965, ns_cookies) - - if ns_cookies: - cookies.extend(ns_cookies) - - return cookies - - def make_cookies(self, response, request): - """Return sequence of Cookie objects extracted from response object. - - See extract_cookies.__doc__ for the interface required of the - response and request arguments. - - """ - self._policy._now = self._now = int(time.time()) - return [cookie for cookie in self._make_cookies(response, request) - if cookie.expires is None or not cookie.expires <= self._now] - - def set_cookie_if_ok(self, cookie, request): - """Set a cookie if policy says it's OK to do so. - - cookie: mechanize.Cookie instance - request: see extract_cookies.__doc__ for the required interface - - """ - self._policy._now = self._now = int(time.time()) - - if self._policy.set_ok(cookie, request): - self.set_cookie(cookie) - - def set_cookie(self, cookie): - """Set a cookie, without checking whether or not it should be set. - - cookie: mechanize.Cookie instance - """ - c = self._cookies - if not c.has_key(cookie.domain): c[cookie.domain] = {} - c2 = c[cookie.domain] - if not c2.has_key(cookie.path): c2[cookie.path] = {} - c3 = c2[cookie.path] - c3[cookie.name] = cookie - - def extract_cookies(self, response, request): - """Extract cookies from response, where allowable given the request. - - Look for allowable Set-Cookie: and Set-Cookie2: headers in the response - object passed as argument. Any of these headers that are found are - used to update the state of the object (subject to the policy.set_ok - method's approval). - - The response object (usually be the result of a call to - mechanize.urlopen, or similar) should support an info method, which - returns a mimetools.Message object (in fact, the 'mimetools.Message - object' may be any object that provides a getheaders method). - - The request object (usually a mechanize.Request instance) must support - the methods get_full_url, get_type, get_host, and is_unverifiable, as - documented by mechanize, and the port attribute (the port number). The - request is used to set default values for cookie-attributes as well as - for checking that the cookie is OK to be set. - - """ - debug("extract_cookies: %s", response.info()) - self._policy._now = self._now = int(time.time()) - - for cookie in self._make_cookies(response, request): - if cookie.expires is not None and cookie.expires <= self._now: - # Expiry date in past is request to delete cookie. This can't be - # in DefaultCookiePolicy, because can't delete cookies there. - try: - self.clear(cookie.domain, cookie.path, cookie.name) - except KeyError: - pass - debug("Expiring cookie, domain='%s', path='%s', name='%s'", - cookie.domain, cookie.path, cookie.name) - elif self._policy.set_ok(cookie, request): - debug(" setting cookie: %s", cookie) - self.set_cookie(cookie) - - def clear(self, domain=None, path=None, name=None): - """Clear some cookies. - - Invoking this method without arguments will clear all cookies. If - given a single argument, only cookies belonging to that domain will be - removed. If given two arguments, cookies belonging to the specified - path within that domain are removed. If given three arguments, then - the cookie with the specified name, path and domain is removed. - - Raises KeyError if no matching cookie exists. - - """ - if name is not None: - if (domain is None) or (path is None): - raise ValueError( - "domain and path must be given to remove a cookie by name") - del self._cookies[domain][path][name] - elif path is not None: - if domain is None: - raise ValueError( - "domain must be given to remove cookies by path") - del self._cookies[domain][path] - elif domain is not None: - del self._cookies[domain] - else: - self._cookies = {} - - def clear_session_cookies(self): - """Discard all session cookies. - - Discards all cookies held by object which had either no Max-Age or - Expires cookie-attribute or an explicit Discard cookie-attribute, or - which otherwise have ended up with a true discard attribute. For - interactive browsers, the end of a session usually corresponds to - closing the browser window. - - Note that the save method won't save session cookies anyway, unless you - ask otherwise by passing a true ignore_discard argument. - - """ - for cookie in self: - if cookie.discard: - self.clear(cookie.domain, cookie.path, cookie.name) - - def clear_expired_cookies(self): - """Discard all expired cookies. - - You probably don't need to call this method: expired cookies are never - sent back to the server (provided you're using DefaultCookiePolicy), - this method is called by CookieJar itself every so often, and the save - method won't save expired cookies anyway (unless you ask otherwise by - passing a true ignore_expires argument). - - """ - now = time.time() - for cookie in self: - if cookie.is_expired(now): - self.clear(cookie.domain, cookie.path, cookie.name) - - def __getitem__(self, i): - if i == 0: - self._getitem_iterator = self.__iter__() - elif self._prev_getitem_index != i-1: raise IndexError( - "CookieJar.__getitem__ only supports sequential iteration") - self._prev_getitem_index = i - try: - return self._getitem_iterator.next() - except StopIteration: - raise IndexError() - - def __iter__(self): - return MappingIterator(self._cookies) - - def __len__(self): - """Return number of contained cookies.""" - i = 0 - for cookie in self: i = i + 1 - return i - - def __repr__(self): - r = [] - for cookie in self: r.append(repr(cookie)) - return "<%s[%s]>" % (self.__class__, ", ".join(r)) - - def __str__(self): - r = [] - for cookie in self: r.append(str(cookie)) - return "<%s[%s]>" % (self.__class__, ", ".join(r)) - - -class LoadError(Exception): pass - -class FileCookieJar(CookieJar): - """CookieJar that can be loaded from and saved to a file. - - Additional methods - - save(filename=None, ignore_discard=False, ignore_expires=False) - load(filename=None, ignore_discard=False, ignore_expires=False) - revert(filename=None, ignore_discard=False, ignore_expires=False) - - Additional public attributes - - filename: filename for loading and saving cookies - - Additional public readable attributes - - delayload: request that cookies are lazily loaded from disk; this is only - a hint since this only affects performance, not behaviour (unless the - cookies on disk are changing); a CookieJar object may ignore it (in fact, - only MSIECookieJar lazily loads cookies at the moment) - - """ - - def __init__(self, filename=None, delayload=False, policy=None): - """ - See FileCookieJar.__doc__ for argument documentation. - - Cookies are NOT loaded from the named file until either the load or - revert method is called. - - """ - CookieJar.__init__(self, policy) - if filename is not None and not isstringlike(filename): - raise ValueError("filename must be string-like") - self.filename = filename - self.delayload = bool(delayload) - - def save(self, filename=None, ignore_discard=False, ignore_expires=False): - """Save cookies to a file. - - filename: name of file in which to save cookies - ignore_discard: save even cookies set to be discarded - ignore_expires: save even cookies that have expired - - The file is overwritten if it already exists, thus wiping all its - cookies. Saved cookies can be restored later using the load or revert - methods. If filename is not specified, self.filename is used; if - self.filename is None, ValueError is raised. - - """ - raise NotImplementedError() - - def load(self, filename=None, ignore_discard=False, ignore_expires=False): - """Load cookies from a file. - - Old cookies are kept unless overwritten by newly loaded ones. - - Arguments are as for .save(). - - If filename is not specified, self.filename is used; if self.filename - is None, ValueError is raised. The named file must be in the format - understood by the class, or LoadError will be raised. This format will - be identical to that written by the save method, unless the load format - is not sufficiently well understood (as is the case for MSIECookieJar). - - """ - if filename is None: - if self.filename is not None: filename = self.filename - else: raise ValueError(MISSING_FILENAME_TEXT) - - f = open(filename) - try: - self._really_load(f, filename, ignore_discard, ignore_expires) - finally: - f.close() - - def revert(self, filename=None, - ignore_discard=False, ignore_expires=False): - """Clear all cookies and reload cookies from a saved file. - - Raises LoadError (or IOError) if reversion is not successful; the - object's state will not be altered if this happens. - - """ - if filename is None: - if self.filename is not None: filename = self.filename - else: raise ValueError(MISSING_FILENAME_TEXT) - - old_state = copy.deepcopy(self._cookies) - self._cookies = {} - try: - self.load(filename, ignore_discard, ignore_expires) - except (LoadError, IOError): - self._cookies = old_state - raise diff -Nru gtkrawgallery-0.9.8/src/mechanize/_debug.py gtkrawgallery-0.9.9/src/mechanize/_debug.py --- gtkrawgallery-0.9.8/src/mechanize/_debug.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/_debug.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -import logging - -from _response import response_seek_wrapper -from _urllib2_fork import BaseHandler - - -class HTTPResponseDebugProcessor(BaseHandler): - handler_order = 900 # before redirections, after everything else - - def http_response(self, request, response): - if not hasattr(response, "seek"): - response = response_seek_wrapper(response) - info = logging.getLogger("mechanize.http_responses").info - try: - info(response.read()) - finally: - response.seek(0) - info("*****************************************************") - return response - - https_response = http_response - -class HTTPRedirectDebugProcessor(BaseHandler): - def http_request(self, request): - if hasattr(request, "redirect_dict"): - info = logging.getLogger("mechanize.http_redirects").info - info("redirecting to %s", request.get_full_url()) - return request diff -Nru gtkrawgallery-0.9.8/src/mechanize/_firefox3cookiejar.py gtkrawgallery-0.9.9/src/mechanize/_firefox3cookiejar.py --- gtkrawgallery-0.9.8/src/mechanize/_firefox3cookiejar.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/_firefox3cookiejar.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,248 +0,0 @@ -"""Firefox 3 "cookies.sqlite" cookie persistence. - -Copyright 2008 John J Lee - -This code is free software; you can redistribute it and/or modify it -under the terms of the BSD or ZPL 2.1 licenses (see the file -COPYING.txt included with the distribution). - -""" - -import logging -import time - -from _clientcookie import CookieJar, Cookie, MappingIterator -from _util import isstringlike, experimental -debug = logging.getLogger("mechanize.cookies").debug - - -class Firefox3CookieJar(CookieJar): - - """Firefox 3 cookie jar. - - The cookies are stored in Firefox 3's "cookies.sqlite" format. - - Constructor arguments: - - filename: filename of cookies.sqlite (typically found at the top level - of a firefox profile directory) - autoconnect: as a convenience, connect to the SQLite cookies database at - Firefox3CookieJar construction time (default True) - policy: an object satisfying the mechanize.CookiePolicy interface - - Note that this is NOT a FileCookieJar, and there are no .load(), - .save() or .restore() methods. The database is in sync with the - cookiejar object's state after each public method call. - - Following Firefox's own behaviour, session cookies are never saved to - the database. - - The file is created, and an sqlite database written to it, if it does - not already exist. The moz_cookies database table is created if it does - not already exist. - """ - - # XXX - # handle DatabaseError exceptions - # add a FileCookieJar (explicit .save() / .revert() / .load() methods) - - def __init__(self, filename, autoconnect=True, policy=None): - experimental("Firefox3CookieJar is experimental code") - CookieJar.__init__(self, policy) - if filename is not None and not isstringlike(filename): - raise ValueError("filename must be string-like") - self.filename = filename - self._conn = None - if autoconnect: - self.connect() - - def connect(self): - import sqlite3 # not available in Python 2.4 stdlib - self._conn = sqlite3.connect(self.filename) - self._conn.isolation_level = "DEFERRED" - self._create_table_if_necessary() - - def close(self): - self._conn.close() - - def _transaction(self, func): - try: - cur = self._conn.cursor() - try: - result = func(cur) - finally: - cur.close() - except: - self._conn.rollback() - raise - else: - self._conn.commit() - return result - - def _execute(self, query, params=()): - return self._transaction(lambda cur: cur.execute(query, params)) - - def _query(self, query, params=()): - # XXX should we bother with a transaction? - cur = self._conn.cursor() - try: - cur.execute(query, params) - return cur.fetchall() - finally: - cur.close() - - def _create_table_if_necessary(self): - self._execute("""\ -CREATE TABLE IF NOT EXISTS moz_cookies (id INTEGER PRIMARY KEY, name TEXT, - value TEXT, host TEXT, path TEXT,expiry INTEGER, - lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)""") - - def _cookie_from_row(self, row): - (pk, name, value, domain, path, expires, - last_accessed, secure, http_only) = row - - version = 0 - domain = domain.encode("ascii", "ignore") - path = path.encode("ascii", "ignore") - name = name.encode("ascii", "ignore") - value = value.encode("ascii", "ignore") - secure = bool(secure) - - # last_accessed isn't a cookie attribute, so isn't added to rest - rest = {} - if http_only: - rest["HttpOnly"] = None - - if name == "": - name = value - value = None - - initial_dot = domain.startswith(".") - domain_specified = initial_dot - - discard = False - if expires == "": - expires = None - discard = True - - return Cookie(version, name, value, - None, False, - domain, domain_specified, initial_dot, - path, False, - secure, - expires, - discard, - None, - None, - rest) - - def clear(self, domain=None, path=None, name=None): - CookieJar.clear(self, domain, path, name) - where_parts = [] - sql_params = [] - if domain is not None: - where_parts.append("host = ?") - sql_params.append(domain) - if path is not None: - where_parts.append("path = ?") - sql_params.append(path) - if name is not None: - where_parts.append("name = ?") - sql_params.append(name) - where = " AND ".join(where_parts) - if where: - where = " WHERE " + where - def clear(cur): - cur.execute("DELETE FROM moz_cookies%s" % where, - tuple(sql_params)) - self._transaction(clear) - - def _row_from_cookie(self, cookie, cur): - expires = cookie.expires - if cookie.discard: - expires = "" - - domain = unicode(cookie.domain) - path = unicode(cookie.path) - name = unicode(cookie.name) - value = unicode(cookie.value) - secure = bool(int(cookie.secure)) - - if value is None: - value = name - name = "" - - last_accessed = int(time.time()) - http_only = cookie.has_nonstandard_attr("HttpOnly") - - query = cur.execute("""SELECT MAX(id) + 1 from moz_cookies""") - pk = query.fetchone()[0] - if pk is None: - pk = 1 - - return (pk, name, value, domain, path, expires, - last_accessed, secure, http_only) - - def set_cookie(self, cookie): - if cookie.discard: - CookieJar.set_cookie(self, cookie) - return - - def set_cookie(cur): - # XXX - # is this RFC 2965-correct? - # could this do an UPDATE instead? - row = self._row_from_cookie(cookie, cur) - name, unused, domain, path = row[1:5] - cur.execute("""\ -DELETE FROM moz_cookies WHERE host = ? AND path = ? AND name = ?""", - (domain, path, name)) - cur.execute("""\ -INSERT INTO moz_cookies VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) -""", row) - self._transaction(set_cookie) - - def __iter__(self): - # session (non-persistent) cookies - for cookie in MappingIterator(self._cookies): - yield cookie - # persistent cookies - for row in self._query("""\ -SELECT * FROM moz_cookies ORDER BY name, path, host"""): - yield self._cookie_from_row(row) - - def _cookies_for_request(self, request): - session_cookies = CookieJar._cookies_for_request(self, request) - def get_cookies(cur): - query = cur.execute("SELECT host from moz_cookies") - domains = [row[0] for row in query.fetchall()] - cookies = [] - for domain in domains: - cookies += self._persistent_cookies_for_domain(domain, - request, cur) - return cookies - persistent_coookies = self._transaction(get_cookies) - return session_cookies + persistent_coookies - - def _persistent_cookies_for_domain(self, domain, request, cur): - cookies = [] - if not self._policy.domain_return_ok(domain, request): - return [] - debug("Checking %s for cookies to return", domain) - query = cur.execute("""\ -SELECT * from moz_cookies WHERE host = ? ORDER BY path""", - (domain,)) - cookies = [self._cookie_from_row(row) for row in query.fetchall()] - last_path = None - r = [] - for cookie in cookies: - if (cookie.path != last_path and - not self._policy.path_return_ok(cookie.path, request)): - last_path = cookie.path - continue - if not self._policy.return_ok(cookie, request): - debug(" not returning cookie") - continue - debug(" it's a match") - r.append(cookie) - return r diff -Nru gtkrawgallery-0.9.8/src/mechanize/_form.py gtkrawgallery-0.9.9/src/mechanize/_form.py --- gtkrawgallery-0.9.8/src/mechanize/_form.py 2013-03-21 04:03:45.000000000 +0000 +++ gtkrawgallery-0.9.9/src/mechanize/_form.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,3280 +0,0 @@ -"""HTML form handling for web clients. - -HTML form handling for web clients: useful for parsing HTML forms, filling them -in and returning the completed forms to the server. This code developed from a -port of Gisle Aas' Perl module HTML::Form, from the libwww-perl library, but -the interface is not the same. - -The most useful docstring is the one for HTMLForm. - -RFC 1866: HTML 2.0 -RFC 1867: Form-based File Upload in HTML -RFC 2388: Returning Values from Forms: multipart/form-data -HTML 3.2 Specification, W3C Recommendation 14 January 1997 (for ISINDEX) -HTML 4.01 Specification, W3C Recommendation 24 December 1999 - - -Copyright 2002-2007 John J. Lee -Copyright 2005 Gary Poster -Copyright 2005 Zope Corporation -Copyright 1998-2000 Gisle Aas. - -This code is free software; you can redistribute it and/or modify it -under the terms of the BSD or ZPL 2.1 licenses (see the file -COPYING.txt included with the distribution). - -""" - -# TODO: -# Clean up post the merge into mechanize -# * Remove code that was duplicated in ClientForm and mechanize -# * Remove weird import stuff -# * Remove pre-Python 2.4 compatibility cruft -# * Clean up tests -# * Later release: Remove the ClientForm 0.1 backwards-compatibility switch -# Remove parser testing hack -# Clean action URI -# Switch to unicode throughout -# See Wichert Akkerman's 2004-01-22 message to c.l.py. -# Apply recommendations from google code project CURLIES -# Apply recommendations from HTML 5 spec -# Add charset parameter to Content-type headers? How to find value?? -# Functional tests to add: -# Single and multiple file upload -# File upload with missing name (check standards) -# mailto: submission & enctype text/plain?? - -# Replace by_label etc. with moniker / selector concept. Allows, e.g., a -# choice between selection by value / id / label / element contents. Or -# choice between matching labels exactly or by substring. etc. - - -__all__ = ['AmbiguityError', 'CheckboxControl', 'Control', - 'ControlNotFoundError', 'FileControl', 'FormParser', 'HTMLForm', - 'HiddenControl', 'IgnoreControl', 'ImageControl', 'IsindexControl', - 'Item', 'ItemCountError', 'ItemNotFoundError', 'Label', - 'ListControl', 'LocateError', 'Missing', 'ParseError', 'ParseFile', - 'ParseFileEx', 'ParseResponse', 'ParseResponseEx','PasswordControl', - 'RadioControl', 'ScalarControl', 'SelectControl', - 'SubmitButtonControl', 'SubmitControl', 'TextControl', - 'TextareaControl', 'XHTMLCompatibleFormParser'] - -import HTMLParser -from cStringIO import StringIO -import inspect -import logging -import random -import re -import sys -import urllib -import urlparse -import warnings - -import _beautifulsoup -import _request - -# from Python itself, for backwards compatibility of raised exceptions -import sgmllib -# bundled copy of sgmllib -import _sgmllib_copy - - -VERSION = "0.2.11" - -CHUNK = 1024 # size of chunks fed to parser, in bytes - -DEFAULT_ENCODING = "latin-1" - -_logger = logging.getLogger("mechanize.forms") -OPTIMIZATION_HACK = True - -def debug(msg, *args, **kwds): - if OPTIMIZATION_HACK: - return - - caller_name = inspect.stack()[1][3] - extended_msg = '%%s %s' % msg - extended_args = (caller_name,)+args - _logger.debug(extended_msg, *extended_args, **kwds) - -def _show_debug_messages(): - global OPTIMIZATION_HACK - OPTIMIZATION_HACK = False - _logger.setLevel(logging.DEBUG) - handler = logging.StreamHandler(sys.stdout) - handler.setLevel(logging.DEBUG) - _logger.addHandler(handler) - - -def deprecation(message, stack_offset=0): - warnings.warn(message, DeprecationWarning, stacklevel=3+stack_offset) - - -class Missing: pass - -_compress_re = re.compile(r"\s+") -def compress_text(text): return _compress_re.sub(" ", text.strip()) - -def normalize_line_endings(text): - return re.sub(r"(?:(? - w = MimeWriter(f) - ...call w.addheader(key, value) 0 or more times... - - followed by either: - - f = w.startbody(content_type) - ...call f.write(data) for body data... - - or: - - w.startmultipartbody(subtype) - for each part: - subwriter = w.nextpart() - ...use the subwriter's methods to create the subpart... - w.lastpart() - - The subwriter is another MimeWriter instance, and should be - treated in the same way as the toplevel MimeWriter. This way, - writing recursive body parts is easy. - - Warning: don't forget to call lastpart()! - - XXX There should be more state so calls made in the wrong order - are detected. - - Some special cases: - - - startbody() just returns the file passed to the constructor; - but don't use this knowledge, as it may be changed. - - - startmultipartbody() actually returns a file as well; - this can be used to write the initial 'if you can read this your - mailer is not MIME-aware' message. - - - If you call flushheaders(), the headers accumulated so far are - written out (and forgotten); this is useful if you don't need a - body part at all, e.g. for a subpart of type message/rfc822 - that's (mis)used to store some header-like information. - - - Passing a keyword argument 'prefix=' to addheader(), - start*body() affects where the header is inserted; 0 means - append at the end, 1 means insert at the start; default is - append for addheader(), but insert for start*body(), which use - it to determine where the Content-type header goes. - - """ - - def __init__(self, fp, http_hdrs=None): - self._http_hdrs = http_hdrs - self._fp = fp - self._headers = [] - self._boundary = [] - self._first_part = True - - def addheader(self, key, value, prefix=0, - add_to_http_hdrs=0): - """ - prefix is ignored if add_to_http_hdrs is true. - """ - lines = value.split("\r\n") - while lines and not lines[-1]: del lines[-1] - while lines and not lines[0]: del lines[0] - if add_to_http_hdrs: - value = "".join(lines) - # 2.2 urllib2 doesn't normalize header case - self._http_hdrs.append((key.capitalize(), value)) - else: - for i in range(1, len(lines)): - lines[i] = " " + lines[i].strip() - value = "\r\n".join(lines) + "\r\n" - line = key.title() + ": " + value - if prefix: - self._headers.insert(0, line) - else: - self._headers.append(line) - - def flushheaders(self): - self._fp.writelines(self._headers) - self._headers = [] - - def startbody(self, ctype=None, plist=[], prefix=1, - add_to_http_hdrs=0, content_type=1): - """ - prefix is ignored if add_to_http_hdrs is true. - """ - if content_type and ctype: - for name, value in plist: - ctype = ctype + ';\r\n %s=%s' % (name, value) - self.addheader("Content-Type", ctype, prefix=prefix, - add_to_http_hdrs=add_to_http_hdrs) - self.flushheaders() - if not add_to_http_hdrs: self._fp.write("\r\n") - self._first_part = True - return self._fp - - def startmultipartbody(self, subtype, boundary=None, plist=[], prefix=1, - add_to_http_hdrs=0, content_type=1): - boundary = boundary or choose_boundary() - self._boundary.append(boundary) - return self.startbody("multipart/" + subtype, - [("boundary", boundary)] + plist, - prefix=prefix, - add_to_http_hdrs=add_to_http_hdrs, - content_type=content_type) - - def nextpart(self): - boundary = self._boundary[-1] - if self._first_part: - self._first_part = False - else: - self._fp.write("\r\n") - self._fp.write("--" + boundary + "\r\n") - return self.__class__(self._fp) - - def lastpart(self): - if self._first_part: - self.nextpart() - boundary = self._boundary.pop() - self._fp.write("\r\n--" + boundary + "--\r\n") - - -class LocateError(ValueError): pass -class AmbiguityError(LocateError): pass -class ControlNotFoundError(LocateError): pass -class ItemNotFoundError(LocateError): pass - -class ItemCountError(ValueError): pass - -# for backwards compatibility, ParseError derives from exceptions that were -# raised by versions of ClientForm <= 0.2.5 -# TODO: move to _html -class ParseError(sgmllib.SGMLParseError, - HTMLParser.HTMLParseError): - - def __init__(self, *args, **kwds): - Exception.__init__(self, *args, **kwds) - - def __str__(self): - return Exception.__str__(self) - - -class _AbstractFormParser: - """forms attribute contains HTMLForm instances on completion.""" - # thanks to Moshe Zadka for an example of sgmllib/htmllib usage - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - if entitydefs is None: - entitydefs = get_entitydefs() - self._entitydefs = entitydefs - self._encoding = encoding - - self.base = None - self.forms = [] - self.labels = [] - self._current_label = None - self._current_form = None - self._select = None - self._optgroup = None - self._option = None - self._textarea = None - - # forms[0] will contain all controls that are outside of any form - # self._global_form is an alias for self.forms[0] - self._global_form = None - self.start_form([]) - self.end_form() - self._current_form = self._global_form = self.forms[0] - - def do_base(self, attrs): - debug("%s", attrs) - for key, value in attrs: - if key == "href": - self.base = self.unescape_attr_if_required(value) - - def end_body(self): - debug("") - if self._current_label is not None: - self.end_label() - if self._current_form is not self._global_form: - self.end_form() - - def start_form(self, attrs): - debug("%s", attrs) - if self._current_form is not self._global_form: - raise ParseError("nested FORMs") - name = None - action = None - enctype = "application/x-www-form-urlencoded" - method = "GET" - d = {} - for key, value in attrs: - if key == "name": - name = self.unescape_attr_if_required(value) - elif key == "action": - action = self.unescape_attr_if_required(value) - elif key == "method": - method = self.unescape_attr_if_required(value.upper()) - elif key == "enctype": - enctype = self.unescape_attr_if_required(value.lower()) - d[key] = self.unescape_attr_if_required(value) - controls = [] - self._current_form = (name, action, method, enctype), d, controls - - def end_form(self): - debug("") - if self._current_label is not None: - self.end_label() - if self._current_form is self._global_form: - raise ParseError("end of FORM before start") - self.forms.append(self._current_form) - self._current_form = self._global_form - - def start_select(self, attrs): - debug("%s", attrs) - if self._select is not None: - raise ParseError("nested SELECTs") - if self._textarea is not None: - raise ParseError("SELECT inside TEXTAREA") - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - - self._select = d - self._add_label(d) - - self._append_select_control({"__select": d}) - - def end_select(self): - debug("") - if self._select is None: - raise ParseError("end of SELECT before start") - - if self._option is not None: - self._end_option() - - self._select = None - - def start_optgroup(self, attrs): - debug("%s", attrs) - if self._select is None: - raise ParseError("OPTGROUP outside of SELECT") - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - - self._optgroup = d - - def end_optgroup(self): - debug("") - if self._optgroup is None: - raise ParseError("end of OPTGROUP before start") - self._optgroup = None - - def _start_option(self, attrs): - debug("%s", attrs) - if self._select is None: - raise ParseError("OPTION outside of SELECT") - if self._option is not None: - self._end_option() - - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - - self._option = {} - self._option.update(d) - if (self._optgroup and self._optgroup.has_key("disabled") and - not self._option.has_key("disabled")): - self._option["disabled"] = None - - def _end_option(self): - debug("") - if self._option is None: - raise ParseError("end of OPTION before start") - - contents = self._option.get("contents", "").strip() - self._option["contents"] = contents - if not self._option.has_key("value"): - self._option["value"] = contents - if not self._option.has_key("label"): - self._option["label"] = contents - # stuff dict of SELECT HTML attrs into a special private key - # (gets deleted again later) - self._option["__select"] = self._select - self._append_select_control(self._option) - self._option = None - - def _append_select_control(self, attrs): - debug("%s", attrs) - controls = self._current_form[2] - name = self._select.get("name") - controls.append(("select", name, attrs)) - - def start_textarea(self, attrs): - debug("%s", attrs) - if self._textarea is not None: - raise ParseError("nested TEXTAREAs") - if self._select is not None: - raise ParseError("TEXTAREA inside SELECT") - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - self._add_label(d) - - self._textarea = d - - def end_textarea(self): - debug("") - if self._textarea is None: - raise ParseError("end of TEXTAREA before start") - controls = self._current_form[2] - name = self._textarea.get("name") - controls.append(("textarea", name, self._textarea)) - self._textarea = None - - def start_label(self, attrs): - debug("%s", attrs) - if self._current_label: - self.end_label() - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - taken = bool(d.get("for")) # empty id is invalid - d["__text"] = "" - d["__taken"] = taken - if taken: - self.labels.append(d) - self._current_label = d - - def end_label(self): - debug("") - label = self._current_label - if label is None: - # something is ugly in the HTML, but we're ignoring it - return - self._current_label = None - # if it is staying around, it is True in all cases - del label["__taken"] - - def _add_label(self, d): - #debug("%s", d) - if self._current_label is not None: - if not self._current_label["__taken"]: - self._current_label["__taken"] = True - d["__label"] = self._current_label - - def handle_data(self, data): - debug("%s", data) - - if self._option is not None: - # self._option is a dictionary of the OPTION element's HTML - # attributes, but it has two special keys, one of which is the - # special "contents" key contains text between OPTION tags (the - # other is the "__select" key: see the end_option method) - map = self._option - key = "contents" - elif self._textarea is not None: - map = self._textarea - key = "value" - data = normalize_line_endings(data) - # not if within option or textarea - elif self._current_label is not None: - map = self._current_label - key = "__text" - else: - return - - if data and not map.has_key(key): - # according to - # http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1 line break - # immediately after start tags or immediately before end tags must - # be ignored, but real browsers only ignore a line break after a - # start tag, so we'll do that. - if data[0:2] == "\r\n": - data = data[2:] - elif data[0:1] in ["\n", "\r"]: - data = data[1:] - map[key] = data - else: - map[key] = map[key] + data - - def do_button(self, attrs): - debug("%s", attrs) - d = {} - d["type"] = "submit" # default - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - controls = self._current_form[2] - - type = d["type"] - name = d.get("name") - # we don't want to lose information, so use a type string that - # doesn't clash with INPUT TYPE={SUBMIT,RESET,BUTTON} - # e.g. type for BUTTON/RESET is "resetbutton" - # (type for INPUT/RESET is "reset") - type = type+"button" - self._add_label(d) - controls.append((type, name, d)) - - def do_input(self, attrs): - debug("%s", attrs) - d = {} - d["type"] = "text" # default - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - controls = self._current_form[2] - - type = d["type"] - name = d.get("name") - self._add_label(d) - controls.append((type, name, d)) - - def do_isindex(self, attrs): - debug("%s", attrs) - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - controls = self._current_form[2] - - self._add_label(d) - # isindex doesn't have type or name HTML attributes - controls.append(("isindex", None, d)) - - def handle_entityref(self, name): - #debug("%s", name) - self.handle_data(unescape( - '&%s;' % name, self._entitydefs, self._encoding)) - - def handle_charref(self, name): - #debug("%s", name) - self.handle_data(unescape_charref(name, self._encoding)) - - def unescape_attr(self, name): - #debug("%s", name) - return unescape(name, self._entitydefs, self._encoding) - - def unescape_attrs(self, attrs): - #debug("%s", attrs) - escaped_attrs = {} - for key, val in attrs.items(): - try: - val.items - except AttributeError: - escaped_attrs[key] = self.unescape_attr(val) - else: - # e.g. "__select" -- yuck! - escaped_attrs[key] = self.unescape_attrs(val) - return escaped_attrs - - def unknown_entityref(self, ref): self.handle_data("&%s;" % ref) - def unknown_charref(self, ref): self.handle_data("&#%s;" % ref) - - -class XHTMLCompatibleFormParser(_AbstractFormParser, HTMLParser.HTMLParser): - """Good for XHTML, bad for tolerance of incorrect HTML.""" - # thanks to Michael Howitz for this! - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - HTMLParser.HTMLParser.__init__(self) - _AbstractFormParser.__init__(self, entitydefs, encoding) - - def feed(self, data): - try: - HTMLParser.HTMLParser.feed(self, data) - except HTMLParser.HTMLParseError, exc: - raise ParseError(exc) - - def start_option(self, attrs): - _AbstractFormParser._start_option(self, attrs) - - def end_option(self): - _AbstractFormParser._end_option(self) - - def handle_starttag(self, tag, attrs): - try: - method = getattr(self, "start_" + tag) - except AttributeError: - try: - method = getattr(self, "do_" + tag) - except AttributeError: - pass # unknown tag - else: - method(attrs) - else: - method(attrs) - - def handle_endtag(self, tag): - try: - method = getattr(self, "end_" + tag) - except AttributeError: - pass # unknown tag - else: - method() - - def unescape(self, name): - # Use the entitydefs passed into constructor, not - # HTMLParser.HTMLParser's entitydefs. - return self.unescape_attr(name) - - def unescape_attr_if_required(self, name): - return name # HTMLParser.HTMLParser already did it - def unescape_attrs_if_required(self, attrs): - return attrs # ditto - - def close(self): - HTMLParser.HTMLParser.close(self) - self.end_body() - - -class _AbstractSgmllibParser(_AbstractFormParser): - - def do_option(self, attrs): - _AbstractFormParser._start_option(self, attrs) - - # we override this attr to decode hex charrefs - entity_or_charref = re.compile( - '&(?:([a-zA-Z][-.a-zA-Z0-9]*)|#(x?[0-9a-fA-F]+))(;?)') - def convert_entityref(self, name): - return unescape("&%s;" % name, self._entitydefs, self._encoding) - def convert_charref(self, name): - return unescape_charref("%s" % name, self._encoding) - def unescape_attr_if_required(self, name): - return name # sgmllib already did it - def unescape_attrs_if_required(self, attrs): - return attrs # ditto - - -class FormParser(_AbstractSgmllibParser, _sgmllib_copy.SGMLParser): - """Good for tolerance of incorrect HTML, bad for XHTML.""" - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - _sgmllib_copy.SGMLParser.__init__(self) - _AbstractFormParser.__init__(self, entitydefs, encoding) - - def feed(self, data): - try: - _sgmllib_copy.SGMLParser.feed(self, data) - except _sgmllib_copy.SGMLParseError, exc: - raise ParseError(exc) - - def close(self): - _sgmllib_copy.SGMLParser.close(self) - self.end_body() - - -class _AbstractBSFormParser(_AbstractSgmllibParser): - - bs_base_class = None - - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - _AbstractFormParser.__init__(self, entitydefs, encoding) - self.bs_base_class.__init__(self) - - def handle_data(self, data): - _AbstractFormParser.handle_data(self, data) - self.bs_base_class.handle_data(self, data) - - def feed(self, data): - try: - self.bs_base_class.feed(self, data) - except _sgmllib_copy.SGMLParseError, exc: - raise ParseError(exc) - - def close(self): - self.bs_base_class.close(self) - self.end_body() - - -class RobustFormParser(_AbstractBSFormParser, _beautifulsoup.BeautifulSoup): - - """Tries to be highly tolerant of incorrect HTML.""" - - bs_base_class = _beautifulsoup.BeautifulSoup - - -class NestingRobustFormParser(_AbstractBSFormParser, - _beautifulsoup.ICantBelieveItsBeautifulSoup): - - """Tries to be highly tolerant of incorrect HTML. - - Different from RobustFormParser in that it more often guesses nesting - above missing end tags (see BeautifulSoup docs). - """ - - bs_base_class = _beautifulsoup.ICantBelieveItsBeautifulSoup - - -#FormParser = XHTMLCompatibleFormParser # testing hack -#FormParser = RobustFormParser # testing hack - - -def ParseResponseEx(response, - select_default=False, - form_parser_class=FormParser, - request_class=_request.Request, - entitydefs=None, - encoding=DEFAULT_ENCODING, - - # private - _urljoin=urlparse.urljoin, - _urlparse=urlparse.urlparse, - _urlunparse=urlparse.urlunparse, - ): - """Identical to ParseResponse, except that: - - 1. The returned list contains an extra item. The first form in the list - contains all controls not contained in any FORM element. - - 2. The arguments ignore_errors and backwards_compat have been removed. - - 3. Backwards-compatibility mode (backwards_compat=True) is not available. - """ - return _ParseFileEx(response, response.geturl(), - select_default, - False, - form_parser_class, - request_class, - entitydefs, - False, - encoding, - _urljoin=_urljoin, - _urlparse=_urlparse, - _urlunparse=_urlunparse, - ) - -def ParseFileEx(file, base_uri, - select_default=False, - form_parser_class=FormParser, - request_class=_request.Request, - entitydefs=None, - encoding=DEFAULT_ENCODING, - - # private - _urljoin=urlparse.urljoin, - _urlparse=urlparse.urlparse, - _urlunparse=urlparse.urlunparse, - ): - """Identical to ParseFile, except that: - - 1. The returned list contains an extra item. The first form in the list - contains all controls not contained in any FORM element. - - 2. The arguments ignore_errors and backwards_compat have been removed. - - 3. Backwards-compatibility mode (backwards_compat=True) is not available. - """ - return _ParseFileEx(file, base_uri, - select_default, - False, - form_parser_class, - request_class, - entitydefs, - False, - encoding, - _urljoin=_urljoin, - _urlparse=_urlparse, - _urlunparse=_urlunparse, - ) - -def ParseString(text, base_uri, *args, **kwds): - fh = StringIO(text) - return ParseFileEx(fh, base_uri, *args, **kwds) - -def ParseResponse(response, *args, **kwds): - """Parse HTTP response and return a list of HTMLForm instances. - - The return value of mechanize.urlopen can be conveniently passed to this - function as the response parameter. - - mechanize.ParseError is raised on parse errors. - - response: file-like object (supporting read() method) with a method - geturl(), returning the URI of the HTTP response - select_default: for multiple-selection SELECT controls and RADIO controls, - pick the first item as the default if none are selected in the HTML - form_parser_class: class to instantiate and use to pass - request_class: class to return from .click() method (default is - mechanize.Request) - entitydefs: mapping like {"&": "&", ...} containing HTML entity - definitions (a sensible default is used) - encoding: character encoding used for encoding numeric character references - when matching link text. mechanize does not attempt to find the encoding - in a META HTTP-EQUIV attribute in the document itself (mechanize, for - example, does do that and will pass the correct value to mechanize using - this parameter). - - backwards_compat: boolean that determines whether the returned HTMLForm - objects are backwards-compatible with old code. If backwards_compat is - true: - - - ClientForm 0.1 code will continue to work as before. - - - Label searches that do not specify a nr (number or count) will always - get the first match, even if other controls match. If - backwards_compat is False, label searches that have ambiguous results - will raise an AmbiguityError. - - - Item label matching is done by strict string comparison rather than - substring matching. - - - De-selecting individual list items is allowed even if the Item is - disabled. - - The backwards_compat argument will be removed in a future release. - - Pass a true value for select_default if you want the behaviour specified by - RFC 1866 (the HTML 2.0 standard), which is to select the first item in a - RADIO or multiple-selection SELECT control if none were selected in the - HTML. Most browsers (including Microsoft Internet Explorer (IE) and - Netscape Navigator) instead leave all items unselected in these cases. The - W3C HTML 4.0 standard leaves this behaviour undefined in the case of - multiple-selection SELECT controls, but insists that at least one RADIO - button should be checked at all times, in contradiction to browser - behaviour. - - There is a choice of parsers. mechanize.XHTMLCompatibleFormParser (uses - HTMLParser.HTMLParser) works best for XHTML, mechanize.FormParser (uses - bundled copy of sgmllib.SGMLParser) (the default) works better for ordinary - grubby HTML. Note that HTMLParser is only available in Python 2.2 and - later. You can pass your own class in here as a hack to work around bad - HTML, but at your own risk: there is no well-defined interface. - - """ - return _ParseFileEx(response, response.geturl(), *args, **kwds)[1:] - -def ParseFile(file, base_uri, *args, **kwds): - """Parse HTML and return a list of HTMLForm instances. - - mechanize.ParseError is raised on parse errors. - - file: file-like object (supporting read() method) containing HTML with zero - or more forms to be parsed - base_uri: the URI of the document (note that the base URI used to submit - the form will be that given in the BASE element if present, not that of - the document) - - For the other arguments and further details, see ParseResponse.__doc__. - - """ - return _ParseFileEx(file, base_uri, *args, **kwds)[1:] - -def _ParseFileEx(file, base_uri, - select_default=False, - ignore_errors=False, - form_parser_class=FormParser, - request_class=_request.Request, - entitydefs=None, - backwards_compat=True, - encoding=DEFAULT_ENCODING, - _urljoin=urlparse.urljoin, - _urlparse=urlparse.urlparse, - _urlunparse=urlparse.urlunparse, - ): - if backwards_compat: - deprecation("operating in backwards-compatibility mode", 1) - fp = form_parser_class(entitydefs, encoding) - while 1: - data = file.read(CHUNK) - try: - fp.feed(data) - except ParseError, e: - e.base_uri = base_uri - raise - if len(data) != CHUNK: break - fp.close() - if fp.base is not None: - # HTML BASE element takes precedence over document URI - base_uri = fp.base - labels = [] # Label(label) for label in fp.labels] - id_to_labels = {} - for l in fp.labels: - label = Label(l) - labels.append(label) - for_id = l["for"] - coll = id_to_labels.get(for_id) - if coll is None: - id_to_labels[for_id] = [label] - else: - coll.append(label) - forms = [] - for (name, action, method, enctype), attrs, controls in fp.forms: - if action is None: - action = base_uri - else: - action = _urljoin(base_uri, action) - # would be nice to make HTMLForm class (form builder) pluggable - form = HTMLForm( - action, method, enctype, name, attrs, request_class, - forms, labels, id_to_labels, backwards_compat) - form._urlparse = _urlparse - form._urlunparse = _urlunparse - for ii in range(len(controls)): - type, name, attrs = controls[ii] - # index=ii*10 allows ImageControl to return multiple ordered pairs - form.new_control( - type, name, attrs, select_default=select_default, index=ii*10) - forms.append(form) - for form in forms: - form.fixup() - return forms - - -class Label: - def __init__(self, attrs): - self.id = attrs.get("for") - self._text = attrs.get("__text").strip() - self._ctext = compress_text(self._text) - self.attrs = attrs - self._backwards_compat = False # maintained by HTMLForm - - def __getattr__(self, name): - if name == "text": - if self._backwards_compat: - return self._text - else: - return self._ctext - return getattr(Label, name) - - def __setattr__(self, name, value): - if name == "text": - # don't see any need for this, so make it read-only - raise AttributeError("text attribute is read-only") - self.__dict__[name] = value - - def __str__(self): - return "" % (self.id, self.text) - - -def _get_label(attrs): - text = attrs.get("__label") - if text is not None: - return Label(text) - else: - return None - -class Control: - """An HTML form control. - - An HTMLForm contains a sequence of Controls. The Controls in an HTMLForm - are accessed using the HTMLForm.find_control method or the - HTMLForm.controls attribute. - - Control instances are usually constructed using the ParseFile / - ParseResponse functions. If you use those functions, you can ignore the - rest of this paragraph. A Control is only properly initialised after the - fixup method has been called. In fact, this is only strictly necessary for - ListControl instances. This is necessary because ListControls are built up - from ListControls each containing only a single item, and their initial - value(s) can only be known after the sequence is complete. - - The types and values that are acceptable for assignment to the value - attribute are defined by subclasses. - - If the disabled attribute is true, this represents the state typically - represented by browsers by 'greying out' a control. If the disabled - attribute is true, the Control will raise AttributeError if an attempt is - made to change its value. In addition, the control will not be considered - 'successful' as defined by the W3C HTML 4 standard -- ie. it will - contribute no data to the return value of the HTMLForm.click* methods. To - enable a control, set the disabled attribute to a false value. - - If the readonly attribute is true, the Control will raise AttributeError if - an attempt is made to change its value. To make a control writable, set - the readonly attribute to a false value. - - All controls have the disabled and readonly attributes, not only those that - may have the HTML attributes of the same names. - - On assignment to the value attribute, the following exceptions are raised: - TypeError, AttributeError (if the value attribute should not be assigned - to, because the control is disabled, for example) and ValueError. - - If the name or value attributes are None, or the value is an empty list, or - if the control is disabled, the control is not successful. - - Public attributes: - - type: string describing type of control (see the keys of the - HTMLForm.type2class dictionary for the allowable values) (readonly) - name: name of control (readonly) - value: current value of control (subclasses may allow a single value, a - sequence of values, or either) - disabled: disabled state - readonly: readonly state - id: value of id HTML attribute - - """ - def __init__(self, type, name, attrs, index=None): - """ - type: string describing type of control (see the keys of the - HTMLForm.type2class dictionary for the allowable values) - name: control name - attrs: HTML attributes of control's HTML element - - """ - raise NotImplementedError() - - def add_to_form(self, form): - self._form = form - form.controls.append(self) - - def fixup(self): - pass - - def is_of_kind(self, kind): - raise NotImplementedError() - - def clear(self): - raise NotImplementedError() - - def __getattr__(self, name): raise NotImplementedError() - def __setattr__(self, name, value): raise NotImplementedError() - - def pairs(self): - """Return list of (key, value) pairs suitable for passing to urlencode. - """ - return [(k, v) for (i, k, v) in self._totally_ordered_pairs()] - - def _totally_ordered_pairs(self): - """Return list of (key, value, index) tuples. - - Like pairs, but allows preserving correct ordering even where several - controls are involved. - - """ - raise NotImplementedError() - - def _write_mime_data(self, mw, name, value): - """Write data for a subitem of this control to a MimeWriter.""" - # called by HTMLForm - mw2 = mw.nextpart() - mw2.addheader("Content-Disposition", - 'form-data; name="%s"' % name, 1) - f = mw2.startbody(prefix=0) - f.write(value) - - def __str__(self): - raise NotImplementedError() - - def get_labels(self): - """Return all labels (Label instances) for this control. - - If the control was surrounded by a