From d021224696e8524cab857c27370e33aeaf024130 Mon Sep 17 00:00:00 2001 From: Ralf Ertzinger Date: Wed, 12 Feb 2014 13:55:05 +0100 Subject: [PATCH 1/1] Checkin of 0.2.98 upstream source --- Authors | 2 + ChangeLog | 952 ++++++++++++++ Makefile | 152 +++ VERSION | 1 + data/Makefile | 23 + data/time-slider.desktop | 44 + data/time-slider.desktop.in | 10 + etc/dbus-1/system.d/time-slider.conf | 48 + etc/xdg/autostart/time-slider-notify.desktop | 10 + lib/svc/method/time-slider | 97 ++ lib/svc/method/time-slider-plugin | 50 + lib/svc/method/time-slider-rsync | 136 ++ po/.intltool-merge-cache | 543 ++++++++ po/ChangeLog | 27 + po/LINGUAS | 21 + po/Makefile | 93 ++ po/POTFILES.in | 12 + po/ar.gmo | Bin 0 -> 13263 bytes po/ar.po | 533 ++++++++ po/ca.gmo | Bin 0 -> 12357 bytes po/ca.po | 535 ++++++++ po/cs.gmo | Bin 0 -> 11697 bytes po/cs.po | 541 ++++++++ po/de.gmo | Bin 0 -> 10630 bytes po/de.po | 868 +++++++++++++ po/es.gmo | Bin 0 -> 10670 bytes po/es.po | 870 +++++++++++++ po/fr.gmo | Bin 0 -> 10807 bytes po/fr.po | 864 +++++++++++++ po/hu.gmo | Bin 0 -> 12415 bytes po/hu.po | 531 ++++++++ po/id.gmo | Bin 0 -> 11784 bytes po/id.po | 541 ++++++++ po/it.gmo | Bin 0 -> 10397 bytes po/it.po | 864 +++++++++++++ po/ja.gmo | Bin 0 -> 11878 bytes po/ja.po | 944 ++++++++++++++ po/ko.gmo | Bin 0 -> 10521 bytes po/ko.po | 856 +++++++++++++ po/nl.gmo | Bin 0 -> 11986 bytes po/nl.po | 550 ++++++++ po/pl.gmo | Bin 0 -> 11738 bytes po/pl.po | 538 ++++++++ po/pt_BR.gmo | Bin 0 -> 10542 bytes po/pt_BR.po | 863 +++++++++++++ po/ru.gmo | Bin 0 -> 385 bytes po/ru.po | 489 ++++++++ po/sv.gmo | Bin 0 -> 385 bytes po/sv.po | 489 ++++++++ po/zh_CN.gmo | Bin 0 -> 9348 bytes po/zh_CN.po | 842 +++++++++++++ po/zh_HK.gmo | Bin 0 -> 9460 bytes po/zh_HK.po | 842 +++++++++++++ po/zh_TW.gmo | Bin 0 -> 9460 bytes po/zh_TW.po | 842 +++++++++++++ py-compile.py | 7 + usr/bin/time-slider-setup | 13 + usr/lib/time-slider-delete | 13 + usr/lib/time-slider-notify | 12 + usr/lib/time-slider-snapshot | 13 + usr/lib/time-slider-version | 11 + usr/lib/time-slider/plugins/rsync/rsync-backup | 10 + usr/lib/time-slider/plugins/rsync/rsync-trigger | 10 + usr/lib/time-slider/plugins/zfssend/zfssend | 10 + usr/lib/time-sliderd | 11 + usr/share/applications/time-slider.desktop | 10 + .../icons/hicolor/16x16/apps/time-slider-setup.png | Bin 0 -> 3383 bytes .../icons/hicolor/24x24/apps/time-slider-setup.png | Bin 0 -> 1304 bytes .../icons/hicolor/32x32/apps/time-slider-setup.png | Bin 0 -> 4614 bytes .../icons/hicolor/36x36/apps/time-slider-setup.png | Bin 0 -> 2151 bytes .../icons/hicolor/48x48/apps/time-slider-setup.png | Bin 0 -> 5750 bytes .../icons/hicolor/72x72/apps/time-slider-setup.png | Bin 0 -> 8633 bytes .../icons/hicolor/96x96/apps/time-slider-setup.png | Bin 0 -> 11053 bytes .../time-slider/glade/time-slider-delete.glade | 567 +++++++++ .../time-slider/glade/time-slider-setup.glade | 531 ++++++++ .../time-slider/glade/time-slider-snapshot.glade | 133 ++ .../time-slider/glade/time-slider-version.glade | 305 +++++ usr/share/time-slider/lib/plugin/__init__.py | 25 + usr/share/time-slider/lib/plugin/plugin.py | 167 +++ usr/share/time-slider/lib/plugin/pluginsmf.py | 66 + usr/share/time-slider/lib/plugin/pluginsmf.py~ | 67 + usr/share/time-slider/lib/plugin/rsync/__init__.py | 25 + usr/share/time-slider/lib/plugin/rsync/backup.py | 1260 +++++++++++++++++++ .../time-slider/lib/plugin/rsync/backup.py.orig | 1264 +++++++++++++++++++ usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py | 89 ++ usr/share/time-slider/lib/plugin/rsync/trigger.py | 138 ++ .../time-slider/lib/plugin/zfssend/__init__.py | 25 + .../time-slider/lib/plugin/zfssend/zfssend.py | 241 ++++ usr/share/time-slider/lib/time_slider/.swp | Bin 0 -> 28672 bytes usr/share/time-slider/lib/time_slider/__init__.py | 52 + usr/share/time-slider/lib/time_slider/applet.py | 645 ++++++++++ usr/share/time-slider/lib/time_slider/applet.py~ | 645 ++++++++++ .../time-slider/lib/time_slider/autosnapsmf.py | 170 +++ usr/share/time-slider/lib/time_slider/dbussvc.py | 103 ++ usr/share/time-slider/lib/time_slider/deletegui.py | 756 +++++++++++ .../time-slider/lib/time_slider/deletegui.py.bak | 766 ++++++++++++ .../time-slider/lib/time_slider/deletegui.py~ | 756 +++++++++++ .../time-slider/lib/time_slider/fileversion.py | 328 +++++ usr/share/time-slider/lib/time_slider/rbac.py | 118 ++ usr/share/time-slider/lib/time_slider/setupgui.py | 1322 ++++++++++++++++++++ usr/share/time-slider/lib/time_slider/setupgui.py~ | 1322 ++++++++++++++++++++ usr/share/time-slider/lib/time_slider/smf.py | 153 +++ usr/share/time-slider/lib/time_slider/snapnowui.py | 224 ++++ .../time-slider/lib/time_slider/snapnowui.py~ | 224 ++++ .../time-slider/lib/time_slider/timesliderd.py | 977 +++++++++++++++ .../time-slider/lib/time_slider/timesliderd.py~ | 977 +++++++++++++++ .../time-slider/lib/time_slider/timeslidersmf.py | 154 +++ usr/share/time-slider/lib/time_slider/tmp.py | 23 + usr/share/time-slider/lib/time_slider/tmp.py~ | 23 + usr/share/time-slider/lib/time_slider/tmp2.py | 183 +++ usr/share/time-slider/lib/time_slider/tmp2.py~ | 183 +++ usr/share/time-slider/lib/time_slider/util.py | 134 ++ usr/share/time-slider/lib/time_slider/util.py~ | 134 ++ usr/share/time-slider/lib/time_slider/zfs.py | 1049 ++++++++++++++++ usr/share/time-slider/lib/time_slider/zfs.py.safe | 1055 ++++++++++++++++ usr/share/time-slider/lib/time_slider/zfs.py~ | 1049 ++++++++++++++++ .../manifest/application/time-slider-plugin.xml | 256 ++++ var/svc/manifest/application/time-slider.xml | 163 +++ .../manifest/system/filesystem/auto-snapshot.xml | 262 ++++ 119 files changed, 34852 insertions(+) create mode 100644 Authors create mode 100644 ChangeLog create mode 100644 Makefile create mode 100644 VERSION create mode 100644 data/Makefile create mode 100644 data/time-slider.desktop create mode 100644 data/time-slider.desktop.in create mode 100644 etc/dbus-1/system.d/time-slider.conf create mode 100644 etc/xdg/autostart/time-slider-notify.desktop create mode 100755 lib/svc/method/time-slider create mode 100755 lib/svc/method/time-slider-plugin create mode 100755 lib/svc/method/time-slider-rsync create mode 100644 po/.intltool-merge-cache create mode 100644 po/ChangeLog create mode 100644 po/LINGUAS create mode 100644 po/Makefile create mode 100644 po/POTFILES.in create mode 100644 po/ar.gmo create mode 100644 po/ar.po create mode 100644 po/ca.gmo create mode 100644 po/ca.po create mode 100644 po/cs.gmo create mode 100644 po/cs.po create mode 100644 po/de.gmo create mode 100644 po/de.po create mode 100644 po/es.gmo create mode 100644 po/es.po create mode 100644 po/fr.gmo create mode 100644 po/fr.po create mode 100644 po/hu.gmo create mode 100644 po/hu.po create mode 100644 po/id.gmo create mode 100644 po/id.po create mode 100644 po/it.gmo create mode 100644 po/it.po create mode 100644 po/ja.gmo create mode 100644 po/ja.po create mode 100644 po/ko.gmo create mode 100644 po/ko.po create mode 100644 po/nl.gmo create mode 100644 po/nl.po create mode 100644 po/pl.gmo create mode 100644 po/pl.po create mode 100644 po/pt_BR.gmo create mode 100644 po/pt_BR.po create mode 100644 po/ru.gmo create mode 100644 po/ru.po create mode 100644 po/sv.gmo create mode 100644 po/sv.po create mode 100644 po/zh_CN.gmo create mode 100644 po/zh_CN.po create mode 100644 po/zh_HK.gmo create mode 100644 po/zh_HK.po create mode 100644 po/zh_TW.gmo create mode 100644 po/zh_TW.po create mode 100644 py-compile.py create mode 100755 usr/bin/time-slider-setup create mode 100755 usr/lib/time-slider-delete create mode 100755 usr/lib/time-slider-notify create mode 100755 usr/lib/time-slider-snapshot create mode 100755 usr/lib/time-slider-version create mode 100755 usr/lib/time-slider/plugins/rsync/rsync-backup create mode 100755 usr/lib/time-slider/plugins/rsync/rsync-trigger create mode 100755 usr/lib/time-slider/plugins/zfssend/zfssend create mode 100755 usr/lib/time-sliderd create mode 100644 usr/share/applications/time-slider.desktop create mode 100644 usr/share/icons/hicolor/16x16/apps/time-slider-setup.png create mode 100644 usr/share/icons/hicolor/24x24/apps/time-slider-setup.png create mode 100644 usr/share/icons/hicolor/32x32/apps/time-slider-setup.png create mode 100644 usr/share/icons/hicolor/36x36/apps/time-slider-setup.png create mode 100644 usr/share/icons/hicolor/48x48/apps/time-slider-setup.png create mode 100644 usr/share/icons/hicolor/72x72/apps/time-slider-setup.png create mode 100644 usr/share/icons/hicolor/96x96/apps/time-slider-setup.png create mode 100644 usr/share/time-slider/glade/time-slider-delete.glade create mode 100644 usr/share/time-slider/glade/time-slider-setup.glade create mode 100644 usr/share/time-slider/glade/time-slider-snapshot.glade create mode 100644 usr/share/time-slider/glade/time-slider-version.glade create mode 100644 usr/share/time-slider/lib/plugin/__init__.py create mode 100644 usr/share/time-slider/lib/plugin/plugin.py create mode 100644 usr/share/time-slider/lib/plugin/pluginsmf.py create mode 100644 usr/share/time-slider/lib/plugin/pluginsmf.py~ create mode 100644 usr/share/time-slider/lib/plugin/rsync/__init__.py create mode 100644 usr/share/time-slider/lib/plugin/rsync/backup.py create mode 100644 usr/share/time-slider/lib/plugin/rsync/backup.py.orig create mode 100755 usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py create mode 100644 usr/share/time-slider/lib/plugin/rsync/trigger.py create mode 100644 usr/share/time-slider/lib/plugin/zfssend/__init__.py create mode 100644 usr/share/time-slider/lib/plugin/zfssend/zfssend.py create mode 100644 usr/share/time-slider/lib/time_slider/.swp create mode 100755 usr/share/time-slider/lib/time_slider/__init__.py create mode 100755 usr/share/time-slider/lib/time_slider/applet.py create mode 100755 usr/share/time-slider/lib/time_slider/applet.py~ create mode 100755 usr/share/time-slider/lib/time_slider/autosnapsmf.py create mode 100644 usr/share/time-slider/lib/time_slider/dbussvc.py create mode 100755 usr/share/time-slider/lib/time_slider/deletegui.py create mode 100644 usr/share/time-slider/lib/time_slider/deletegui.py.bak create mode 100755 usr/share/time-slider/lib/time_slider/deletegui.py~ create mode 100755 usr/share/time-slider/lib/time_slider/fileversion.py create mode 100755 usr/share/time-slider/lib/time_slider/rbac.py create mode 100755 usr/share/time-slider/lib/time_slider/setupgui.py create mode 100755 usr/share/time-slider/lib/time_slider/setupgui.py~ create mode 100644 usr/share/time-slider/lib/time_slider/smf.py create mode 100755 usr/share/time-slider/lib/time_slider/snapnowui.py create mode 100755 usr/share/time-slider/lib/time_slider/snapnowui.py~ create mode 100755 usr/share/time-slider/lib/time_slider/timesliderd.py create mode 100755 usr/share/time-slider/lib/time_slider/timesliderd.py~ create mode 100755 usr/share/time-slider/lib/time_slider/timeslidersmf.py create mode 100755 usr/share/time-slider/lib/time_slider/tmp.py create mode 100755 usr/share/time-slider/lib/time_slider/tmp.py~ create mode 100755 usr/share/time-slider/lib/time_slider/tmp2.py create mode 100755 usr/share/time-slider/lib/time_slider/tmp2.py~ create mode 100644 usr/share/time-slider/lib/time_slider/util.py create mode 100644 usr/share/time-slider/lib/time_slider/util.py~ create mode 100755 usr/share/time-slider/lib/time_slider/zfs.py create mode 100755 usr/share/time-slider/lib/time_slider/zfs.py.safe create mode 100755 usr/share/time-slider/lib/time_slider/zfs.py~ create mode 100644 var/svc/manifest/application/time-slider-plugin.xml create mode 100644 var/svc/manifest/application/time-slider.xml create mode 100644 var/svc/manifest/system/filesystem/auto-snapshot.xml diff --git a/Authors b/Authors new file mode 100644 index 0000000..4670b17 --- /dev/null +++ b/Authors @@ -0,0 +1,2 @@ +2008-09-11 Niall Power - + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..69803fe --- /dev/null +++ b/ChangeLog @@ -0,0 +1,952 @@ +2011-04-27 Erwann Chenede - + + * VERSION: Bump to 0.2.98 + * usr/share/time-slider/lib/time_slider/applet.py + usr/share/time-slider/lib/time_slider/deletegui.py + usr/share/time-slider/lib/time_slider/setupgui.py + usr/share/time-slider/lib/time_slider/snapnowui.py + usr/share/time-slider/lib/time_slider/timesliderd.py : + Fix for 7038498 removed Primary Administrator references + + * usr/share/time-slider/lib/time_slider/util.py + usr/share/time-slider/lib/time_slider/zfs.py : + Fix for 6996354 gracefully handle snapshot failure + +2010-07-29 Niall Power - + + * VERSION: Bump to 0.2.97 + +2010-07-08 Niall Power - + + * usr/share/time-slider/lib/time_slider/timesliderd.py: + Fix typo when raising exception. Defect #16361 + RunTimeError -> RuntimeError + +2010-07-05 Niall Power - + + * usr/share/time-slider/lib/time_slider/setupgui.py: + Don't set filesystem tree view size request based on + number of filesystems: Doesn't scale well. Set fixed + initial size instead. Fixes defect #16319 + Correct misspellings of "writable and "administrator". + Fixes defect #16388 + + * usr/share/time-slider/lib/time_slider/zfs.py: + Check returned value of bisect.bisect_left() is within + index range of list before trying to dereference it and + causing IndexError exceptions. Fixes defect #16377. + Thanks to Tim Foster for this fix. + +2010-06-16 Niall Power - + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Remove accidental clobbering of tempSchedule list so + that non-archived backups can be expired as per normal + schedule retention rules. Opensolaris defect #16280 + +2010-06-04 Niall Power - + + * usr/share/time-slider/lib/time_slider/applet.py: + Register menu callback in Note class instantiation to + avoid multiple registration of same callback. + +2010-05-24 Niall Power - + + * usr/share/time-slider/lib/time_slider/setupgui.py: + Fix minor function spelling typo + +2010-05-24 Niall Power - + + * usr/share/time-slider/glade/time-slider-setup.glade: + Replace GtkFileChooser combo widget with standard + GtkComboBox + + * usr/share/time-slider/lib/time_slider/applet.py: + User path_tol_volume() from util library instead of + private copy + + * usr/share/time-slider/lib/time_slider/setupgui.py: + Replace standard GtkFileChooser logic with custom combo + box implementation that is more tailored towards selecting + a backup device rather than a generic path. Is able to + handle an offline/unmounted previously configured backup + device gracefully (impossible with stock widget). Doesn't + show meaningless shortcuts like Home, Documents, etc. either + Cleanup of code, renamed variables to use camel case and + made unnecessarily public class variables private. + Changed order of target validation tests to check if the + device supports posix style links before checking if it's + empty. Means user doesn't have to delete everything on a + PCFS/FAT32 device only then to find out it's still unsuitable. + + * usr/share/time-slider/lib/time_slider/util.py: Move previously + private function path_to_volume(path) here so it can be shared. + +2010-05-14 Niall Power - + + * usr/share/time-slider/lib/time_slider/applet.py: + Use icon list hinted by gio.Volume instead of hardcoded + harddisk icon when possible when displaying notifications. + Makes icon more context specific to type of backup device. + +2010-05-14 Niall Power - + + * usr/share/time-slider/lib/time_slider/applet.py: + Determine volume name of rsync target if available and + display that instead of the raw mount point in + notifications. More user friendly. + +2010-05-14 Erwann Chenede - + + * usr/share/time-slider/glade/time-slider-delete.glade: + Added Type combobox + * usr/share/time-slider/lib/plugin/pluginsmf.py: + fix import to call this class from a non standard + location + * usr/share/time-slider/lib/time_slider/deletegui.py: + implemented rsync backup visualization and deletion + * usr/share/time-slider/lib/time_slider/zfs.py: + renamed member function destroy_snapshot to destroy + to reflect allow polymorphic call in deletegui.py + as the list of backup to delete can now be snapshots + or rsync backups. + * usr/share/time-slider/lib/time_slider/timesliderd.py: + propagated member function destroy name change. + +2010-05-14 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Fix list ValueError in list_pending_snapshots() caused + when rsync plugin is enabled but no fileystems are + selected for backup. Check that zfs returns something + more than a blank in such cases. + +2010-05-14 Niall Power - + + * usr/share/time-slider/glade/time-slider-setup.glade: + Fix resizing issue that prevented list view from expanding + to occupy all extra space when window gets vertically + extended. + +2010-05-14 Niall Power - + + * etc/dbus-1/system.d/time-slider.conf: + Add service and policy definitions for TimSlider.config + service. + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Deal with backup device being mounted under variable + removable media mount points if not found in it's expected + location. Find and validate alternate mount points. + + * usr/share/time-slider/lib/time_slider/applet.py: + Refactored to allow more modular notification management. + Improved rsync device monitoring by implementing both gio.File + and gio.Volume monitors so we can track mounting/unmounting of + static mounts like nfs/zfs and variable mount points for + hotpluggable devices. Validate rsync target is correct using + SMF configuration key. Listen to TimeSlider.Config D-Bus events + and refresh configuration when D-Bus notification is received. + Add menu item to enable launching of setup GUI from the applet. + + * usr/share/time-slider/lib/time_slider/dbussvc.py: + Add class and method definition for TimeSlider Config service + notifications. + + * usr/share/time-slider/lib/time_slider/setupgui.py: + Register with system D-Bus and send notification of changes + to time-slider and rsync plugin services so the applet can + refresh. + +2010-05-13 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Fix list IndexError exception caused by looking + for non-existent backups on an empty device. + +2010-05-11 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Tweak the space management of the rsync device for better + performance: + - don't scan for deleteables unless the device starts to + exceed threshhold capacity. + - Rescan capacity periodically during rsync backup thread + lifecycle and rescan for deleteable backups only if + device exceeds threshold capacity level. + +2010-05-11 Niall Power - + + * var/svc/manifest/system/filesystem/auto-snapshot.xml: + Update service bundle name and version number to + SUNWtime-slider and 0.2.96 respectively. + + * var/svc/manifest/application/time-slider-plugin.xml: + Add 2 new properties to define cleanup threshhold level of + rsync backup device and verbosity of rsync output for + debugging purposes. + + * usr/share/time-slider/lib/time_slider/util.py: Adjust + signature of util.debug() to make the verbose argument + mandatory instead of automatic. + + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: + Add 2 new methods to retrieve cleanup threshhold and rsync + verbosity properties from the rsync SMF service instance. + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Cleanup variable naming, making class private variables + actually private using leading "_" character. + Rename RsyncBackup class to RsyncProcess to me more descriptive. + Add support for rsync verbosity in RsyncProcess class. + Implement better cleanup and space management mechanism: + - Removes old backups in parallel to new rsync backups running + in a separate thread if space falls below threshhold level. + - Identifies what backups can and can't be deleted for a given + new rsync backup based on age and number of backups left for + the associated filesystem. + - Cleanup threshhold tuneable via SMF atic. + + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: + Add 2 new methods to retrieve cleanup threshhold and rsync + verbosity properties from the rsync SMF service instance. + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Add check to make sure config key on target device matches that + stored in SMF before commencing backups. + Cleanup variable naming, making class private variables + actually private using leading "_" character. + Rename RsyncBackup class to RsyncProcess to me more descriptive. + Add support for rsync verbosity in RsyncProcess class. + Implement better cleanup and space management mechanism: + - Removes old backups in parallel to new rsync backups running + in a separate thread if space falls below threshhold level. + Avoids having to make rough, inaccurate guesses about size + required for new backups because it responds in real time. + - Identifies what backups can and can't be deleted for a given + new rsync backup based on age and number of backups left for + the associated filesystem. + - Cleanup threshhold tuneable via SMF. + - Doesn't try to backup snapshots older than what's already on + the backup device if device is almost full. + +2010-05-05 Niall Power - + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Redesigned queue management and backlog mechanism. Backs up + most recent snapshot set first, then works backwards through + the queue. Stops backup mechanism falling too far behind while + still allowing backup of older snapshots to take place if + time and space permit. + Enhancements to filesystem layout of backups - added seperate + folders for partial transfers, log files, trash folder and lock + files. Added file locking mechanism to prevent deletion by other + tools of backups in use (eg during incremental backups). + Added trash mechanism, replaces in place deletion of snapshots + which messes up directory mtimes and incremental backups. + Instead, expired backups are moved to a .trash folder and a new + method: empty_trash_folders() cleans it up after before starting + the next snapshot set backup. + Fix bug in list_pending_snapshots() where ctime of snapshots was + returned as a string instead of a long. + + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: Add new + filesystem suffix definitions for partial backups, trash + folders, backup lock files and backup log files. + +2010-05-03 Niall Power - + + * usr/share/time-slider/lib/time_slider/timesliderd.py: + Determine if SMF property is set to true. + Do not perform remedial cleanups if it's value is false. + + * usr/share/time-slider/lib/time_slider/timeslidersmf.py: + Add new method: get_remedial_cleanup() to indicate the value of + property + + * var/svc/manifest/application/time-slider.xml: + Add new SMF property: as boolean with + default value of 'true'. Indicates that time-sliderd should + perform remedial cleanups when snapshotted pool runs low on + space. + +2010-05-03 Niall Power - + + * Makefile: + Install/uninstall time-slider.desktop into + $(DESTDIR)/usr/share/applications/ + +2010-05-02 Niall Power - + + * lib/svc/method/time-slider: + Remove any legacy zfs-auto-snapshot cron jobs in + zfssnap's crontab that may not have been removed + if the old auto-snapshot services were not stopped + cleanly. + +2010-05-02 Niall Power - + + * usr/share/time-slider/lib/time_slider/timesliderd.py: + Fix incorrect invocation of util.debug() that caused + unconditional verbose output. + +2010-05-02 Niall Power - + + * usr/share/time-slider/lib/time_slider/timesliderd.py: + Fix small typo in monthly calculation. + +2010-05-02 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Instantiate RsyncBackup objects with verbose flag + passed through. Remove "--progress" argument from + rsync and add "-vv" argument instead if verbose + flag is set. + +2010-05-01 Niall Power - + + * usr/share/time-slider/lib/plugin/plugin.py: Fix some + incorrect invocations that didn't pass verbose + parameter (and were therefore no-ops) + + * usr/share/time-slider/lib/time_slider/timesliderd.py: + Fix monthly period calculation. Now handles period + increments > 12 months and period increments that + land on december (originally logged as bugster + CR #6904417) + +2010-05-01 Niall Power - + + * usr/share/time-slider/lib/time_slider/setupgui.py: + Cache all GUI configuration values at startup and + use them later to compare changes in configuration + state. Only apply the minimum set of configuration + changes necessary instead of previous method of + blindly applying everything, even if unchanged. + Configuration is much much faster now, especially + for minor configuration changes. + Recognise previously configured backup devices and + ask the user if they want to start using it again + instead of rejecting it as belonging to another + system. + Remove duplicate import of os.path modules. + +2010-04-28 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Add removal of partial backups missing a matching + snapshot in the backup queue. They are zombies. + When catching an rsync exception, change directory + to "/" so that pfexec doesn't freak out when trying + to release the snapshot. pfexec bails out when it + tries to add cwd to it's path if cwd is non-existent + which can be the case if the backup device suddenly + goes offline. + +2010-04-27 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Remember to also delete log files when deleting backups. + +2010-04-27 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Exit if effective UID of process is not 0 (root) + +2010-04-27 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: + Send rsync loggint to individual log files on the backup + device. Delete log files when corresponding backup gets + deleted. + Create rsync backups in a temporary directory first and + move to permanent location when completed. Prevents + incomplete backups appearing in backup view in Nautilus. + Also use "--inplace" rsync option to allow resumption of + partial (interrupted) backups. + + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: + Add shared key get/set methods for rsync SMF instance. + Use new "set_string_prop" Smf class method for setting + rsync target directory so that path names containing + spaces can be correctly set. Conversely, strip out '\ ' + space formatting characters returned by SMF in + get_target_dir() method. + + * usr/share/time-slider/lib/plugin/rsync/trigger.py: + Don't tag new snapshots whose filesystems are not mounted + Prevents piling up of snapshots from unmounted BEs. Might + be best to make this behaviour configurable via SMF. + + * usr/share/time-slider/lib/time_slider/setupgui.py: + Clean up OK button clicked callback by farming out several + validation checks to separate methods. Add rsync config + checking method to chek configuration/selection of rsync + target device. Implement shared key checking to validate/ + identify pre-configured backup devices. Rejects devices + configured for use on other systems. Add checks to ensure + target device is writable by root and supports hard links + as required for rsync incremental backups. Add a random + key generation function. Perform basic space checking on + rsync target device to see if it's big enough. + + * usr/share/time-slider/lib/time_slider/smf.py: + Implement set_string_prop() method that allows white space + string characters. + + * var/svc/manifest/application/time-slider-plugin.xml: + Remove rsync plugin's property override of + and inherit defaults instead. Backing + up frequent snapshots caused near constant backup disk + churn. + +2010-04-16 Niall Power - + + * usr/share/time-slider/lib/time_slider/zfs.py: Fix bug in + method "create_auto_snapshot_set() where it would skip + the entire pool if the root of the pool was tagged and + all datastets under the root inherited from it. Also, + use sorted lists and binary searching to speed up the + operation and avoid making repeated unnecessary calls + to Dataset.list_children() (the data is already provided) + +2010-04-15 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: Clean up + dangling holds on snapshots left by abnormally terminated + rsync transfers from previous invocations. Also, release + snapshot holds if an Rsync exception is caught during + transfer. Allows time-sliderd to delete snapshots when they + naturally expire. + +2010-04-13 Niall Power - + * usr/share/time-slider/lib/time_slider/setupgui.py: + Fix broken target directory retrieval from FileChooser button + +2010-04-13 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: Create a + RsyncBackup class to run rsync backup commands in a + separate thread. Define Rsync exception clases and map + to expected rsync exit codes. + Modify toplevel backup path for backups to begin with: + /TIMESLIDER// + Deal with rsync exceptions raised and place into maintenance + state if non recoverable. + Try to recover space on target directory (work in progress) + Add debugging and error logging output. + + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: define + backup RSYNCDIRPREFIX to "TIMESLIDER". + + * usr/share/time-slider/lib/time_slider/applet.py: + Use new backup path format as described above. + + * usr/share/time-slider/lib/time_slider/setupgui.py: + Use new backup path format as described above. + + * usr/share/time-slider/lib/time_slider/util.py: + Add general purpose syslog wrapper function. + + * var/svc/manifest/application/time-slider-plugin.xml: + remove accidental definition of + Set to empty string value "" instead of a directory on my + own system. Doh. + +2010-04-01 Niall Power - + + * usr/share/time-slider/lib/time_slider/timesliderd.py: insert a + 'h' character between hour and minute values of auto snapshot + labels as a subsitute for the previously removed ':' character + used in time stamps. Makes life easier for nautilus component + and plays nicely with strptime() + +2010-04-01 Niall Power - + + * usr/share/time-slider/lib/time_slider/applet.py: gio FileMonitor + events are inconsistent between UFS/ZFS filesystem mounting. + Ignore event type from gio FileMonitor and verify backup target + status manually using os.stat() + +2010-04-01 Niall Power - + + * usr/share/time-slider/lib/time_slider/applet.py: Improve string + formatting on notifications and tooltips. Stop the status icon + blinking once the notification has been closed. + * usr/share/time-slider/lib/time_slider/setupgui.py: Forgot to + connect rsync folder selection to rsync SMF instance + configuration. Added. Also use get_current_folder() instead of + get_filename() to get correct folder from FileChooserButton + widget. + +2010-03-31 Niall Power - + + * usr/share/time-slider/lib/time_slider/applet.py: Restrict manual + synchronisation menu item to priviliged users only (root or + users assigned Primary Administrator profile). Also, don't + bother popping up a right click menu if it's empty. + +2010-03-31 Niall Power - + + * usr/share/time-slider/lib/plugin/rsync/backup.py: Implement cleanup + of expired backups on rsync target directory. Uses same rules as + time-slider as defined by each auto-snapshot schedule's SMF instance + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: Add a method to + return the list of archived schedules (ie. ones that don't get + cleaned according to the normal schedule retention rules) + * usr/share/time-slider/lib/time_slider/applet.py: add a + 'Synchronize Now' menu item to the applet. Added gio based file + monitoring to keep track of when the rsync backup target directory + is mounted/unmounted and adjust menu sensitivty accordingly. + * usr/share/time-slider/lib/time_slider/autosnapsmf.py: Define + SNAPLABELPREFIX as a constant for snapshot label names + * usr/share/time-slider/lib/time_slider/setupgui.py: Modify RBAC + rules to ensure that setup GUI is run with euid of root which is + necessary for validation and initialisation of rsync backup target + directory + * usr/share/time-slider/lib/time_slider/timesliderd.py: use + SNAPLABELPREFIX constant instead of local multiply defined values. + * var/svc/manifest/application/time-slider-plugin.xml: Override + "plugin/trigger_on" value in rsync plugin manifest. Add a new + property "rsync/archived_schedules" to define snapshot schedules + that are archived as long as possible and not purged according + to the normal schule retention rules. + +2010-03-25 Niall Power - + + * etc/dbus-1/system.d/time-slider.conf: add policies for + rsync plugin's dbus service and methods. + * usr/share/time-slider/lib/plugin/rsync/backup.py: restructure script + to perform backups via a Gobject mainloop which is necessary to allow + it to send dbus notifications correctly. Adjust backup target dir + to use a rsync plugin specific subdirectory on the target dir + (.time-slider/rsync) - prevents borkage of mount points with + unmounted filesystems. Use "-a" option with rsync command which + covers all the desired arguments fo our rsync usage case (archival) + Add dbus method invocations. General cleanup and removal of commented + out code. Use lockfile to prevent multiple instances running at once. + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: Add definitions + for backup subdirectory and filesystem property tag constants. + * usr/share/time-slider/lib/plugin/rsync/trigger.py: Use constants as + defined in rsyncsmf.py + * usr/share/time-slider/lib/time_slider/applet.py: Incorporate new + RsyncNote class and other pieces from Krishnan Parthasarathi. Thanks + Krishan. Added signal handlers and tooltips/popups for dbus events + sent from rsync plugin. + * usr/share/time-slider/lib/time_slider/dbussvc.py: New dbus signal + definitions added, including a few from Krishnan. + * usr/share/time-slider/lib/time_slider/setupgui.py: Perform validation + on target directory for rsync. Not exhaustive yet. Checks that target + directory isn't on a file system or pool that's been selected for + automatic snapshots. + +2010-03-11 Niall Power - + + Add rsync plugin (WORK IN PROGRESS), restructure SMF related code + and restructure file/directory layout to provide seperate + subdirectories for plugins. + + * Makefile: Add/remove files to (un)install rules. + * usr/lib/time-sliderd: Fix script invocation to invoke python2.6 + explicitly (ie. do not use /usr/bin/env to source it) + * usr/share/time-slider/glade/time-slider-setup.glade: Add in UI + features to enable rsync backup configuration + * usr/share/time-slider/lib/time_slider/__init__.py: adjust sys.path + to allow importation from the plugin directory + * usr/share/time-slider/lib/time_slider/autosnapsmf.py: Refactor to + inehrit from the smf base object class (smf.py) + * usr/share/time-slider/lib/time_slider/deletegui.py: Remove import + of smfmanager module. + * usr/share/time-slider/lib/time_slider/setupgui.py: Remove import of + smfmanager module. Implement UI logic and view for rsync + configuration (filechooser, list view check buttons etc.) + Tie in with rsync plugin SMF backend. WORK IN PROGRESS. + * usr/share/time-slider/lib/time_slider/timesliderd.py: Drop use of old + smfmanager class and replace with new smf and timeslidersmf modules + * usr/share/time-slider/lib/time_slider/zfs.py: Misc. cleanups. Define + proper Exception classes for ZFS and ZPool errors and raise them when + appropriate. Old style string based exceptions not supported in python + 2.6 + * var/svc/manifest/application/time-slider-plugin.xml: Define rsync + plugin SMF instance and properties. + * usr/lib/time-slider-zfssend: Removed. Replaced by: + usr/lib/time-slider/plugins/zfssend/zfssend + * usr/share/time-slider/lib/time_slider/plugin.py: Removed. Replaced by: + usr/share/time-slider/lib/plugin/plugin.py + * usr/share/time-slider/lib/time_slider/zfssend.py: Removed. Replaced by: + usr/share/time-slider/lib/plugin/zfssend/zfssend.py + * lib/svc/method/time-slider-rsync: Added. Starts stops rsync plugin SMF + instance and sets up cron job for rsync-backup script. + * usr/lib/time-slider/plugins/rsync/rsync-backup: Added. Performs + asynchronouse rsync backup of zfs snapshots previously tagged for backup. + WORK IN PROGRESS + * usr/lib/time-slider/plugins/rsync/rsync-trigger: Provides trigger + mechanisem for time-slider to call when snapshots created. Queues up + zfs snapshots for later backup using rsync-backup script. + * usr/lib/time-slider/plugins/zfssend/zfssend: Replacement of + usr/lib/time-slider-zfssend + * usr/share/time-slider/lib/plugin/__init__.py: Added. Initialises new + plugin module + * usr/share/time-slider/lib/plugin/plugin.py: Replacement of: + usr/share/time-slider/lib/time_slider/plugin.py + * usr/share/time-slider/lib/plugin/pluginsmf.py: Added. Defines plugin + SMF class specialised for time-slider plugin SMF instances + * usr/share/time-slider/lib/plugin/rsync/__init__.py: Added. Initialises + new rsync plugin module. + * usr/share/time-slider/lib/plugin/rsync/backup.py: Added. Main code + block for rsync-backup script. + * usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py: Added. Defines + SMF class specialised for rsync plugin SMF instance. + * usr/share/time-slider/lib/plugin/rsync/trigger.py: Added. Main code + block for rsync-trigger script. + * usr/share/time-slider/lib/plugin/zfssend/__init__.py: Added. + Initialises zfssend plugin module. + * usr/share/time-slider/lib/plugin/zfssend/zfssend.py: Replacement for + usr/share/time-slider/lib/time_slider/zfssend.py + * usr/share/time-slider/lib/time_slider/smf.py: Added. Provides cleaner + and more structured base class for generic SMF instances and and is + inherited by timeslidersmf, pluginsmf, rsyncsmf derived classes. + Provides common methods to enable/disable and get/set property values + and refresh and query service state. + * usr/share/time-slider/lib/time_slider/timeslidersmf.py: Defines + SMF class and methods specific to the time-slider SMF instances. + +2010-02-04 Niall Power - + + * Makefike: Add lib/svc/method/time-slider-plugin to + install/uninstall rules. + * lib/svc/method/time-slider-plugin: Added. + Provides simple, generic plugin start method. + * var/svc/manifest/application/time-slider-plugin.xml: + Added comments for time-slider-plugin method, advising + that non trivial plugin instances should provide their + own specific methods. + +2010-02-03 Niall Power - + + * var/svc/manifest/application/time-slider.xml: + Define new property "zfs/sep". Taken for zfs-auto-snapshot, + allows custom definition of a separator character for snapshot + datestamps that can work on filesystems that previously choked + on the ":" characted such as CIFS, PCFS. + * usr/share/time-slider/lib/time_slider/smfmanager.py: Added new + method to get zfs/sep property. + * usr/share/time-slider/lib/time_slider/timesliderd.py: modify + snapshot creation and listing operations to use the new zfs/sep + property, while maintaining compatibility with old snapshot + datestamps that used the ":" character. + +2010-02-02 Niall Power - + + * usr/share/time-slider/lib/time_slider/plugin.py: + Fix invalid function call "debug()" should be "util.debug()" when + reporting disabled plugin in PluginManager.refresh() + Missing "()" in method call Plugin.is_running() in Plugin.refresh() + +2010-01-26 Niall Power - + + * VERSION: bump to 0.2.96 + * Use explicit path: "/usr/bin/python2.6" to reference python interpreter + instead of relying on "/usr/bin/env" to find it. + * Add plugin framework to enable user defined plugins to be executed after + snapshots get taken by time-sliderd. Plugins are based on the SMF service + framework and plugins are defined as instances of the base SMV service: + svc:/application/time-slider/plugin + * Provide a "zfs-send" plugin as a replacement for the zfs-auto-snapshot + provided "backup-save-cmd" property. + * Add util.py to define two commoonly used utility functions: debug/logging + and command spawning. Reduces lines of code and duplication. + * Refactor all python files to use util.run_command() convenience function + where possible. + * Fix a bug where time-sliderd emits warnings and tries to cleanup pools + upon which it creates no snapshots. Also make time-sliderd more descriminating + in general and able to identify snapshots it created from backups of snapshots + it create (via zfs send/receive) by checking for the auto-snapshot property + corresponding to the filesystem/volume of snapshots. + * TODO: SMF code is a bit all over the place and could do with proper stucturing + +2009-11-27 Niall Power - + + * usr/share/time-slider/lib/time_slider/timesliderd.py: fixed a date + calculation bug whereby a schedule with a period in months next falls + due on December. Was getting incorrectly modulused to 0. + +2009-11-04 Niall Power - + + * VERSION: bump to 0.2.95 + * Move to python2.6 + * Drop dependency on zfs-auto-snapshot methods and implement our own + snapshotting mechanism in the form of time-sliderd + * Add dbus system bus notification and notification applet, replacing + the previous ugly hack of su to logged in user ID and running + notify-send. + * Enhance all .py modules to use python subprocess module when spawning + commands instead of os.popen. + * Add SMF manifest for system/filesystem/auto-snapshot to replace + systems snapshot configuration previously supplied by + zfs-auto-snapshot. Generally compatible but cleaned up by dropping + some of zfs-auto-snapshot's more quirky or deprecated configuration + options. + +2009-07-08 Niall Power - + + * VERSION: bump to 0.2.10 + * Fix defect 8667 in following source files: + * usr/share/time-slider/lib/time_slider/cleanupmanager.py: Refactor + code to modified zfs.py APIs, remove some dead code, make capacity + checks conditional where possible. + * usr/share/time-slider/lib/time_slider/deletegui.py: minor code + refactoring to match zfs.py API changes. + * usr/share/time-slider/lib/time_slider/setupgui.py: Remove references + to zfscontroller module which has been removed. Uses zfs.py + exclusively now. Refactor for zfs.py changes + * usr/share/time-slider/lib/time_slider/zfscontroller.py: removed. + Very little functionality and nothing that shouldn't be in zfs.py + * usr/share/time-slider/lib/time_slider/zfs/py: New class definition + for "Datasets" which stores all system snapshots, filesystems and + volumes in a cache. Existing Dataset, Filesystem, Snapshot and Volume + class methods now use the global Datasets cache for querying and + listing instead of making zfs(1m) spawned commands. + +2009-05-18 Niall Power - + + * usr/share/time-slider/lib/time_slider/cleanupmanager.py + Cleanup both Filesystem and Volume snapshots. Fixes d.o.o 8454 + * usr/share/time-slider/lib/time_slider/setupgui.py: Prepend private + class methods with "__" in accordance with python coding standards. + * usr/share/time-slider/lib/time_slider/smfmanager.py: Use predefined + macros for ZFS commands instead of hardcoding path in the code. + * usr/share/time-slider/lib/time_slider/zfs.py: Improve object + hierarchy and inheritance tree. Rename some methods for improved + clarity of purpose. Improve method and function documentation. + Add capability to deal with ZFS volumes. Fixes d.o.o 8454 & 8685 + +2009-05-13 Harry Fu - + + * VERSION: bump to 0.2.9 + +2009-04-10 Erwann Chenede - + + * /usr/share/time-slider/lib/time_slider/fileversion.py: + used gobject.idle_add instead of gtk.gdk.threads_* fixes + 7319 + +2009-04-09 Erwann Chenede - + + * /usr/share/time-slider/lib/time_slider/fileversion.py: + removed hard dependency on external thumbnailer and meld + fixes #7502 + +2009-04-07 Niall Power + + * usr/share/time-slider/lib/time_slider/cleanupmanager.py + * usr/share/time-slider/lib/time_slider/deletegui.py + * usr/share/time-slider/lib/time_slider/smfmanager.py + * usr/share/time-slider/lib/time_slider/zfs.py + * usr/share/time-slider/lib/time_slider/zfscontroller.py: + Use macro substitution to specify full paths to all spawned + commands (zfs, zpool, svcadm, svccfg, svcprop, pfexec) so that + commands work independently of user's PATH environment. + Fixes opensolaris defect #7396 + +2009-03-01 Niall Power + + * VERSION: bump to 0.2.6 + * Makefile: Pick up PYTHON environment variable. Enhancement for + fix to 6754650 + * usr/share/time-slider/lib/time_slider/setupgui.py: Allow filesystems + to inherit properties from their parent instead of locally tagging + every filesystem with the required property setting. + * usr/share/time-slider/lib/time_slider/zfs.py: Add an "inherit" argument + to Filesystem.commit_state() to allow the fileystem to inherit from + it's parent when setting the auto-snapshot property. + +2009-02-19 Takao Fujiwara + + * usr/share/time-slider/lib/time_slider/deletegui.py: updated + the comments because translators are confused it. + * usr/share/time-slider/lib/time_slider/snapnowui.py: updated + the gettext "%s" so that translators can change the oder of "%s". + +2009-02-05 Niall Power - + + * VERSION: bump to 0.2.5 + * Makefile: don't install .pyc files, but generate them as part + of "install:" rule. Fixes bugster: 6754650 + * py-compile.py: Prepend "DESTDIR" to compile python bytecode + within the installed directory tree. + * usr/share/time-slider/lib/time_slider/__init__.pyc: removed + * usr/share/time-slider/lib/time_slider/cleanupmanager.pyc: removed + * usr/share/time-slider/lib/time_slider/deletegui.pyc: removed + * usr/share/time-slider/lib/time_slider/fileversion.pyc: removed + * usr/share/time-slider/lib/time_slider/notification.pyc: removed + * usr/share/time-slider/lib/time_slider/rbac.pyc: removed + * usr/share/time-slider/lib/time_slider/setupgui.pyc: removed + * usr/share/time-slider/lib/time_slider/smfmanager.pyc: removed + * usr/share/time-slider/lib/time_slider/snapnowui.pyc: removed + * usr/share/time-slider/lib/time_slider/zfs.pyc: removed + * usr/share/time-slider/lib/time_slider/zfscontroller.pyc: removed + + +2009-01-23 Erwann Chenede - + + * VERSION: bump version to 0.2.4 + * Makefile : added new files + * usr/lib/time-slider-version + * usr/share/time-slider/glade/time-slider-version.glade + * usr/share/time-slider/lib/time_slider/fileversion.py + * usr/share/time-slider/lib/time_slider/fileversion.pyc : + Initial implementation of the file version explorer app + +2009-01-16 Takao Fujiwara + + * po/POTFILES.in: Update with the latest files. + * usr/share/time-slider/glade/time-slider-delete.glade: + * usr/share/time-slider/glade/time-slider-setup.glade: + * usr/share/time-slider/glade/time-slider-snapshot.glade: + Update to remove "translatable" tag for GTK textdomain. + * usr/share/time-slider/lib/time_slider/deletegui.py: + Update to localize date and add the encoding conversion. + * usr/share/time-slider/lib/time_slider/setupgui.py: + Update to localize "legacy". + * usr/share/time-slider/lib/time_slider/snapnowui.py: + Update to add gettext. + +2009-01-09 Niall Power - + + * VERSION: bump version to 0.2.3 + * usr/share/time-slider/glade/time-slider-delete.glade: + add progress dialog for snapshot scanning feedback + * usr/share/time-slider/lib/time_slider/deletegui.py: + restructure initialisation code and run snapshot scanning in it's + own thread so that feedback can be give to user via a progress dialog + Prevents the app appearing dead when dealing with huge numbers of + snapshots. + +2009-01-09 Erwann Chenede - + + * Makefile: added newly introduced file + * usr/share/time-slider/lib/time_slider/snapnowui.py: removed print + +2009-01-09 Erwann Chenede - + + * usr/lib/time-slider-snapshot: + * usr/share/time-slider/glade/time-slider-snapshot.glade: + * usr/share/time-slider/lib/time_slider/snapnowui.py: + Initial implementation of the snapshot now dialog + +2008-12-17 Niall Power - + + * VERSION: bump version to 0.2.2 + * lib/svc/method/time-slider: remove "set -x" command which errantly turns + on debug mode. Fixes bugzilla defect #5067 + * usr/share/time-slider/glade/time-slider-setup.glade: Change mnemonic key for + "Custom" radio button from "C" to "u". C is already in use by "Cancel" button. + Fixes bugzilla defect #3986 + * usr/share/time-slider/lib/time_slider/setupgui.py: Change error dialog text to + instruct user to consult "svcs -xv" instead of "svcs(1) man page" for more info + when time-slider SMF instance is placed into offline state by SMF. + Fixes bugzilla defect #5804 + +2008-12-17 Niall Power - + + * usr/share/time-slider/lib/time_slider/cleanupmanager.py: Check + ps commmand output before accessing it by catching IndexError + exceptions. Prevents root's mailbox being spammed by cron. + Fixes bugster #6758575 + +2008-12-12 Niall Power - + + * usr/share/time-slider/lib/time_slider/cleanupmanager.py: Implement + destruction of older snapshots since zfs-auto-snapshot only does + snapshot destruction recursively and we break the recursion by + deleting individual snapshots instead of recursive sets. + +2008-12-05 Niall Power - + * usr/share/time-slider/lib/time_slider/setupgui.py + Fixed a patch generation problem when launching time-slider-delete + +2008-12-05 Niall Power - + + * VERSION: bumped to 0.2.1 + * Makefile: Minor cleanups + * usr/share/time-slider/lib/time_slider/cleanupmanager.py: add + perform_purge method to clean up zero sized snapshots under + safe circumstances so that the system doesn't become cluttered + with too many permanently zero sized snapshots. + Rewrote emercency space makeing cleanup alogorithm in run_cleanup + to be more efficient and spawn far fewer shell commands. No longer + usr/share/time-slider/lib/time_slider/setupgui.py: Change error dialog text to + instruct user to consult "svcs -xv" instead of "svcs(1) man page" for more info + when time-slider SMF instance is placed into offline state by SMF. + Fixes bugzilla defect #5804 + does recursive snapshot deletion since the above changes will break + the recursive snapshot chain in most cases and recursive deletion + is now overkill since individual snapshots should be larger in size + and yield larger space savings per snapshot deleted. + Fixed notification bug where notification would happen twice if + gnome-session is launched via dbus-launch. + * usr/share/time-slider/lib/time_slider/deletegui.py: move + __get_cloned_snapshots() method to a public function in zfs.py + * usr/share/time-slider/lib/time_slider/notification.py: fix typo + bug in send_to_desktop() where initial warning level was set to + 90000 which broke desktop notification. Should be 0. + * usr/share/time-slider/lib/time_slider/zfs.py: Add new function: + list_cloned_snapshots() to list snapshots that have cloned filesystem + based on them. + +2008-12-02 Niall Power - + + * Makefile: Fix install target error that didn't install both glade + files + +2008-12-02 Niall Power - + + * VERSION: bumped to 0.2.0 + * Makefile: added new files to install and uninstall targets + * data/Makefile: adjusted for renamed .desktop.in file + * usr/share/time-slider/glade/time-slider-setup.glade: Added launch + button for snapshot deletion GUI. Rename window title from + "Time Slider Setup" to "Time Slider Manager" + * usr/share/time-slider/lib/time_slider/setupgui.py: add launch handler + for time-slider-delete + * usr/share/time-slider/lib/time_slider/zfs.py: Add utility functions + for time-slider-delete for listing and creating snapshot objects + * data/time-slider.desktop.in renamed from time-slider-setup.desktop.in + * usr/lib/time-slider-delete: Added new snapshot deleter program to + allow snapshot browsing/deletion. + * usr/share/applications/time-slider.desktop: renamef from + time-slider-setup.desktop + * usr/share/time-slider/glade/time-slider-delete.glade: Added glade UI + file for snapshot delete GUI + * usr/share/time-slider/lib/time_slider/deletegui.py: Added main python + code for listing and deleting snapshots + +2008-12-02 Niall Power - + + * py-compile.py: Added to insure pyc files are in sync with + .py sources. Fixes bugster #6754650 + * VERSION: Added version file. Bump to 0.1.5 + * SUNWgnome-time-slider.spec: removd because spec file is + in spec-files-other repository on opensolaris.org + * Makefile: added "dist" target for creating dist bz2 tarballs + +2008-10-23 Erwann Chenede - + + * usr/share/icons/hicolor/16x16/apps/time-slider-setup.png + * usr/share/icons/hicolor/24x24/apps/time-slider-setup.png + * usr/share/icons/hicolor/32x32/apps/time-slider-setup.png + * usr/share/icons/hicolor/36x36/apps/time-slider-setup.png + * usr/share/icons/hicolor/48x48/apps/time-slider-setup.png + * usr/share/icons/hicolor/72x72/apps/time-slider-setup.png : + new icon (4182) + +2008-10-20 Takao Fujiwara + + * Makefile: Updated to load po/Makefile and data/Makefile. + * data/Makefile: Added to generate .desktop files. + * data/time-slider-setup.desktop.in: Added for intltool. + * po: Added for intltool. + +2008-09-11 Niall Power - + + * Initial import of Time Slider service + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..346ebe8 --- /dev/null +++ b/Makefile @@ -0,0 +1,152 @@ +include VERSION + +mkinstalldirs = /usr/bin/mkdir -p +INSTALL = /usr/sbin/install +INSTALL_DATA = ${INSTALL} -u root -g bin -m 644 -f +INSTALL_PROGRAM = ${INSTALL} -u root -g bin -f +INSTALL_SCRIPT = ${INSTALL} -f +RM = /usr/bin/rm -f +RMRF = /usr/bin/rm -Rf +RMDIR = /usr/bin/rmdir +# Use python 2.6 if PYTHON environent is not set +ifeq ($(strip $(PYTHON)),) +PYTHON = /usr/bin/python2.6 +endif + +SUBDIRS = po data + +DISTFILES = Authors \ + VERSION \ + ChangeLog \ + Makefile \ + py-compile.py \ + $(SUBDIRS) \ + lib \ + usr \ + var \ + etc + +clean: + $(RM) usr/share/time-slider/lib/time_slider/*.pyc + $(RM) usr/share/time-slider/lib/plugin/*.pyc + $(RM) usr/share/time-slider/lib/plugin/rsync/*.pyc + $(RM) usr/share/time-slider/lib/plugin/zfssend/*.pyc + +all: + for subdir in $(SUBDIRS); do \ + cd $$subdir; make; cd ..;\ + done + echo $(VERSION) + +dist: clean all + $(RMRF) time-slider-$(VERSION) + mkdir time-slider-$(VERSION) + cp -pR $(DISTFILES) time-slider-$(VERSION) + /usr/bin/tar cf - time-slider-$(VERSION) | bzip2 > time-slider-$(VERSION).tar.bz2 + $(RMRF) time-slider-$(VERSION) + +install: + for subdir in $(SUBDIRS); do \ + cd $$subdir; \ + make DESTDIR=$(DESTDIR) GETTEXT_PACKAGE=time-slider install; \ + cd ..;\ + done + $(mkinstalldirs) $(DESTDIR)/etc/dbus-1/system.d + $(INSTALL_DATA) $(DESTDIR)/etc/dbus-1/system.d etc/dbus-1/system.d/time-slider.conf + $(mkinstalldirs) $(DESTDIR)/etc/xdg/autostart + $(INSTALL_DATA) $(DESTDIR)/etc/xdg/autostart etc/xdg/autostart/*.desktop + $(mkinstalldirs) $(DESTDIR)/lib/svc/method + $(INSTALL_SCRIPT) $(DESTDIR)/lib/svc/method lib/svc/method/time-slider + $(INSTALL_SCRIPT) $(DESTDIR)/lib/svc/method lib/svc/method/time-slider-plugin + $(INSTALL_SCRIPT) $(DESTDIR)/lib/svc/method lib/svc/method/time-slider-rsync + $(mkinstalldirs) $(DESTDIR)/usr/bin + $(INSTALL_PROGRAM) $(DESTDIR)/usr/bin usr/bin/time-slider-setup + $(mkinstalldirs) $(DESTDIR)/usr/lib/time-slider/plugins/rsync + $(mkinstalldirs) $(DESTDIR)/usr/lib/time-slider/plugins/zfssend + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib usr/lib/time-sliderd + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib usr/lib/time-slider-delete + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib usr/lib/time-slider-notify + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib usr/lib/time-slider-snapshot + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib usr/lib/time-slider-version + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib/time-slider/plugins/zfssend usr/lib/time-slider/plugins/zfssend/zfssend + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib/time-slider/plugins/rsync usr/lib/time-slider/plugins/rsync/rsync-trigger + $(INSTALL_PROGRAM) $(DESTDIR)/usr/lib/time-slider/plugins/rsync usr/lib/time-slider/plugins/rsync/rsync-backup + $(mkinstalldirs) $(DESTDIR)/usr/share/applications + $(INSTALL_DATA) $(DESTDIR)/usr/share/applications usr/share/applications/time-slider.desktop + $(mkinstalldirs) $(DESTDIR)/usr/share/icons/hicolor/16x16/apps + $(INSTALL_DATA) $(DESTDIR)/usr/share/icons/hicolor/16x16/apps usr/share/icons/hicolor/16x16/apps/time-slider-setup.png + $(mkinstalldirs) $(DESTDIR)/usr/share/icons/hicolor/24x24/apps + $(INSTALL_DATA) $(DESTDIR)/usr/share/icons/hicolor/24x24/apps usr/share/icons/hicolor/24x24/apps/time-slider-setup.png + $(mkinstalldirs) $(DESTDIR)/usr/share/icons/hicolor/32x32/apps + $(INSTALL_DATA) $(DESTDIR)/usr/share/icons/hicolor/32x32/apps usr/share/icons/hicolor/32x32/apps/time-slider-setup.png + $(mkinstalldirs) $(DESTDIR)/usr/share/icons/hicolor/36x36/apps + $(INSTALL_DATA) $(DESTDIR)/usr/share/icons/hicolor/36x36/apps usr/share/icons/hicolor/36x36/apps/time-slider-setup.png + $(mkinstalldirs) $(DESTDIR)/usr/share/icons/hicolor/48x48/apps + $(INSTALL_DATA) $(DESTDIR)/usr/share/icons/hicolor/48x48/apps usr/share/icons/hicolor/48x48/apps/time-slider-setup.png + $(mkinstalldirs) $(DESTDIR)/usr/share/icons/hicolor/72x72/apps + $(INSTALL_DATA) $(DESTDIR)/usr/share/icons/hicolor/72x72/apps usr/share/icons/hicolor/72x72/apps/time-slider-setup.png + $(mkinstalldirs) $(DESTDIR)/usr/share/icons/hicolor/96x96/apps + $(INSTALL_DATA) $(DESTDIR)/usr/share/icons/hicolor/96x96/apps usr/share/icons/hicolor/96x96/apps/time-slider-setup.png + $(mkinstalldirs) $(DESTDIR)/usr/share/time-slider/glade + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/glade usr/share/time-slider/glade/time-slider-delete.glade + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/glade usr/share/time-slider/glade/time-slider-setup.glade + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/glade usr/share/time-slider/glade/time-slider-snapshot.glade + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/glade usr/share/time-slider/glade/time-slider-version.glade + $(mkinstalldirs) $(DESTDIR)/usr/share/time-slider/lib/time_slider + for file in usr/share/time-slider/lib/time_slider/*.py; do \ + if test -f $$file ; then \ + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/lib/time_slider $$file; \ + fi; \ + done + $(mkinstalldirs) $(DESTDIR)/usr/share/time-slider/lib/plugin + for file in usr/share/time-slider/lib/plugin/*.py; do \ + if test -f $$file ; then \ + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/lib/plugin $$file; \ + fi; \ + done + $(mkinstalldirs) $(DESTDIR)/usr/share/time-slider/lib/plugin/rsync + for file in usr/share/time-slider/lib/plugin/rsync/*.py; do \ + if test -f $$file ; then \ + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/lib/plugin/rsync $$file; \ + fi; \ + done + $(mkinstalldirs) $(DESTDIR)/usr/share/time-slider/lib/plugin/zfssend + for file in usr/share/time-slider/lib/plugin/zfssend/*.py; do \ + if test -f $$file ; then \ + $(INSTALL_DATA) $(DESTDIR)/usr/share/time-slider/lib/plugin/zfssend $$file; \ + fi; \ + done + $(mkinstalldirs) $(DESTDIR)/var/svc/manifest/application + $(INSTALL_DATA) $(DESTDIR)/var/svc/manifest/application var/svc/manifest/application/time-slider.xml + $(INSTALL_DATA) $(DESTDIR)/var/svc/manifest/application var/svc/manifest/application/time-slider-plugin.xml + $(mkinstalldirs) $(DESTDIR)/var/svc/manifest/system/filesystem + $(INSTALL_DATA) $(DESTDIR)/var/svc/manifest/system/filesystem var/svc/manifest/system/filesystem/auto-snapshot.xml + $(PYTHON) py-compile.py + +uninstall: + for subdir in $(SUBDIRS); do \ + cd $$subdir; \ + make DESTDIR=$(DESTDIR) GETTEXT_PACKAGE=time-slider uninstall; \ + cd ..;\ + done + $(RM) $(DESTDIR)/etc/dbus-1/system.d/time-slider.conf + $(RM) $(DESTDIR)/etc/xdg/autostart/time-slider-notify.desktop + $(RM) $(DESTDIR)/lib/svc/method/time-slider + $(RM) $(DESTDIR)/lib/svc/method/time-slider-plugin + $(RM) $(DESTDIR)/lib/svc/method/time-slider-rsync + $(RM) $(DESTDIR)/usr/bin/time-slider-setup + $(RM) $(DESTDIR)/usr/lib/time-sliderd + $(RM) $(DESTDIR)/usr/lib/time-slider-delete + $(RM) $(DESTDIR)/usr/lib/time-slider-notify + $(RM) $(DESTDIR)/usr/lib/time-slider-snapshot + $(RM) $(DESTDIR)/usr/lib/time-slider-version + $(RM) $(DESTDIR)/usr/lib/time-slider-zfssend + $(RM) $(DESTDIR)/usr/lib/time-slider-rsync + $(RMRF) $(DESTDIR)/usr/lib/time-slider/plugins/rsync + $(RMRF) $(DESTDIR)/usr/lib/time-slider/plugins/zfssend + $(RM) $(DESTDIR)/usr/share/applications/time-slider.desktop + $(RM) $(DESTDIR)/usr/share/icons/hicolor/*/apps/time-slider-setup.png + $(RMRF) $(DESTDIR)/usr/share/time-slider + $(RM) $(DESTDIR)/var/svc/manifest/application/time-slider.xml + $(RM) $(DESTDIR)/var/svc/manifest/application/time-slider-plugin.xml + $(RM) $(DESTDIR)/var/svc/manifest/system/filesystem/auto-snapshot.xml diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8e15db9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +VERSION = 0.2.98 diff --git a/data/Makefile b/data/Makefile new file mode 100644 index 0000000..4855402 --- /dev/null +++ b/data/Makefile @@ -0,0 +1,23 @@ + +top_builddir = .. +top_srcdir = .. +mkinstalldirs = /usr/bin/mkdir -p +INSTALL = /usr/sbin/install +INSTALL_DATA = ${INSTALL} -u root -g bin -m 644 -f +RM = /usr/bin/rm -f +RMDIR = /usr/bin/rmdir +INTLTOOL_MERGE = /usr/bin/intltool-merge +desktop_in_files = time-slider.desktop.in +desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) + +all: $(desktop_DATA) + +install: + $(mkinstalldirs) $(DESTDIR)/usr/share/applications + $(INSTALL_DATA) $(DESTDIR)/usr/share/applications time-slider.desktop + +uninstall: + $(RM) $(DESTDIR)/usr/share/applications/time-slider.desktop + +%.desktop: %.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@ + diff --git a/data/time-slider.desktop b/data/time-slider.desktop new file mode 100644 index 0000000..7ecf0a8 --- /dev/null +++ b/data/time-slider.desktop @@ -0,0 +1,44 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Time Slider +Name[ar]=شريط تمرير الوقت +Name[ca]=Time Slider +Name[cs]=Time Slider +Name[de]=Zeit-Schieberegler +Name[es]=Deslizador de tiempo +Name[fr]=Curseur temporel +Name[hu]=Time Slider +Name[id]=Time Slider +Name[it]=Time Slider +Name[ja]=タイムスライダ +Name[ko]=시간 슬라이더 +Name[nl]=Tijd glijder +Name[pl]=Time Slider +Name[pt_BR]=Barra deslizante de tempo +Name[zh_CN]=时间滑块 +Name[zh_HK]=時間滑動軸 +Name[zh_TW]=時間滑動軸 +Comment=Configure the system to take automatic snapshots of your data +Comment[ar]=تاكيد النظام على أخذ صورة عن النظام عن البيانات +Comment[ca]=Configurar el sistema per a fer instantànies automàtiques de les vostres dades +Comment[cs]=Nakonfigurujte si systém tak, aby vytvářel automatické snímky VaÅ¡ich dat +Comment[de]=System für automatische Snapshots Ihrer Daten konfigurieren +Comment[es]=Configurar el sistema para tomar instantáneas de manera automática de los datos +Comment[fr]=Configurer le système de manière à prendre des instantanés automatiques des données +Comment[hu]=A rendszer beállítása arra, hogy automatikusan mentse az Ön adatait +Comment[id]= +Comment[it]=Configurare il sistema per fare delle istantanee automatiche dei propri dati +Comment[ja]=自動的にデータのスナップショットを作成するようにシステムを構成します +Comment[ko]=사용자 데이터의 자동 스냅샷을 만들도록 시스템 구성 +Comment[nl]=Configureer het systeem om automatische momentopnamen van je gegevens te nemen +Comment[pl]=Skonfiguruj system do automatycznego wykonywania migawek twoich danych +Comment[pt_BR]=Configurar o sistema para realizar instantâneos automáticos dos dados +Comment[zh_CN]=将系统配置为自动捕获数据的快照 +Comment[zh_HK]=將系統配置為自動對資料執行快照 +Comment[zh_TW]=將系統配置為自動對資料執行快照 +Terminal=false +Type=Application +StartupNotify=true +Exec=time-slider-setup +Icon=time-slider-setup +Categories=Application;System;Settings diff --git a/data/time-slider.desktop.in b/data/time-slider.desktop.in new file mode 100644 index 0000000..a4f945a --- /dev/null +++ b/data/time-slider.desktop.in @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +_Name=Time Slider +_Comment=Configure the system to take automatic snapshots of your data +Terminal=false +Type=Application +StartupNotify=true +Exec=time-slider-setup +Icon=time-slider-setup +Categories=Application;System;Settings diff --git a/etc/dbus-1/system.d/time-slider.conf b/etc/dbus-1/system.d/time-slider.conf new file mode 100644 index 0000000..10506c5 --- /dev/null +++ b/etc/dbus-1/system.d/time-slider.conf @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/etc/xdg/autostart/time-slider-notify.desktop b/etc/xdg/autostart/time-slider-notify.desktop new file mode 100644 index 0000000..06af0ff --- /dev/null +++ b/etc/xdg/autostart/time-slider-notify.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Time Slider notification applet +Comment=Show Time Slider status and notifications +Icon=time-slider-setup +Exec=/usr/lib/time-slider-notify +Terminal=false +Type=Application +Categories= +X-GNOME-Autostart-enabled=true diff --git a/lib/svc/method/time-slider b/lib/svc/method/time-slider new file mode 100755 index 0000000..4ad7614 --- /dev/null +++ b/lib/svc/method/time-slider @@ -0,0 +1,97 @@ +#!/bin/ksh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" + +. /lib/svc/share/smf_include.sh + + +# Given the exit status of a command, an integer, 0 if the command completed +# without errors. If the command exited with errors we degrade the +# state of this service into maintenance mode. If a 3rd argument is presented +# we don't degrade the service. We also log an error message as passed into +# this function. +# +function check_failure { # integer exit status, error message, non-fatal flag + + typeset RESULT=$1 + typeset ERR_MSG=$2 + typeset NON_FATAL=$3 + + if [ $RESULT -ne 0 ] ; then + print_log "Error: $ERR_MSG" + if [ -z "${NON_FATAL}" ] ; then + print_log "Moving service $SMF_FMRI to maintenance mode." + svcadm mark maintenance $SMF_FMRI + fi + fi + +} + + +# A function we use to emit output. Right now, this goes to syslog via logger(1) +# as well as being echoed to stdout which will result in it being picked up by +# SMF if the $LOG variable is null. +# +function print_log { # message to display + logger -t time-slider -p daemon.notice $* + echo $* +} + +# this function removes any left over cron jobs belonging to +# legacy zfs-auto-snapshot +# +function remove_legacy_cronjobs { + + crontab -l | grep -v "/lib/svc/method/zfs-auto-snapshot" \ + > /tmp/saved-crontab.$$ + + crontab /tmp/saved-crontab.$$ + check_failure $? "Unable to remove legacy zfs-auto-snaphot cron jobs" "NON_FATAL" + + rm /tmp/saved-crontab.$$ + +} + +case "$1" in +'start') + remove_legacy_cronjobs + + [ ! -x /usr/lib/time-sliderd ] && exit $SMF_EXIT_ERR_CONFIG + + /usr/lib/time-sliderd + err=$? + if [ $err -ne 0 ]; then + echo "Time Slider failed to start: error $err" + exit $SMF_EXIT_ERR_FATAL + fi + ;; +*) + echo "Usage: $0 { start }" + exit $SMF_EXIT_ERR_FATAL + ;; +esac + +exit $SMF_EXIT_OK diff --git a/lib/svc/method/time-slider-plugin b/lib/svc/method/time-slider-plugin new file mode 100755 index 0000000..02744b4 --- /dev/null +++ b/lib/svc/method/time-slider-plugin @@ -0,0 +1,50 @@ +#!/bin/ksh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2010 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" + +. /lib/svc/share/smf_include.sh + +case "$1" in +'start') + PLUGIN_CMD="$(svcprop -p plugin/command $SMF_FMRI)" + if [ $? -ne 0 ] ; then + echo "Unable to obtain plugin invocation command" + exit $SMF_EXIT_ERR_CONFIG + fi + + if [ ! -x $PLUGIN_CMD ] ; then + echo "\"$PLUGIN_CMD\" is not an executable path" + exit $SMF_EXIT_ERR_CONFIG + fi + ;; +*) + echo "Usage: $0 { start }" + exit $SMF_EXIT_ERR_FATAL + ;; +esac + +exit $SMF_EXIT_OK + diff --git a/lib/svc/method/time-slider-rsync b/lib/svc/method/time-slider-rsync new file mode 100755 index 0000000..a570dad --- /dev/null +++ b/lib/svc/method/time-slider-rsync @@ -0,0 +1,136 @@ +#!/bin/ksh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# A rather simple SMF method script for +# svc:/application/time-slider:default +# It's sole purpose is to set the correct SMF property value of it's +# direct "auto-snapshot:" dependencies: zfs/fs-name = '//' + +. /lib/svc/share/smf_include.sh + +RSYNC_PROG="/usr/lib/time-slider/plugins/rsync/rsync-backup" + +# This function sets the appropriate svc configuration property for all +# dependent auto-snapshot: instances if necessary +function update_service_props { + DEPENDENCIES=$(svcs -d -H -o fmri $SMF_FMRI|grep auto-snapshot) + for dependency in ${DEPENDENCIES} ; do + if [ "$(svcprop -p zfs/fs-name $dependency)" != "//" ] ; then + svccfg -s $dependency setprop zfs/fs-name = astring: "//" + svcadm refresh $dependency + fi + done +} + +# Given the exit status of a command, an integer, 0 if the command completed +# without errors. If the command exited with errors we degrade the +# state of this service into maintenance mode. If a 3rd argument is presented +# we don't degrade the service. We also log an error message as passed into +# this function. +# +function check_failure { # integer exit status, error message to display, be fatal + + typeset RESULT=$1 + typeset ERR_MSG=$2 + typeset NON_FATAL=$3 + + if [ $RESULT -ne 0 ] ; then + print_log "Error: $ERR_MSG" + print_log "Moving service $SMF_FMRI to maintenance mode." + if [ -z "${NON_FATAL}" ] ; then + print_log "Moving service $SMF_FMRI to maintenance mode." + svcadm mark maintenance $SMF_FMRI + fi + fi + +} + +# A function we use to emit output. Right now, this goes to syslog via logger(1) +function print_log { # message to display + logger -t time-slider -p daemon.notice $* +} + + +function add_rsync_cronjob { + # Call every 30 minutes, 15 and 45 minutes after the hour. + SCHEDULE="15,45 * * * *" + + # adding a cron job is essentially just looking for an existing entry, + # removing it, and appending a new one. Neato. + crontab -l | grep -v "${RSYNC_PROG} $SMF_FMRI" \ + > /tmp/saved-crontab.$$ + + echo "${SCHEDULE} ${RSYNC_PROG} $SMF_FMRI" \ + >> /tmp/saved-crontab.$$ + + crontab /tmp/saved-crontab.$$ + check_failure $? "Unable to add cron job!" + + rm /tmp/saved-crontab.$$ + return 0 +} + +function remove_rsync_cronjob { + + crontab -l | grep -v "${RSYNC_PROG} $SMF_FMRI$" \ + > /tmp/saved-crontab.$$ + crontab /tmp/saved-crontab.$$ + check_failure $? "Unable to unschedule cron job for $SMF_FMRI" + + rm /tmp/saved-crontab.$$ + + # finally, check our status before we return + STATE=$(svcprop -p restarter/state $SMF_FMRI) + if [ "${STATE}" == "maintenance" ] ; then + STATE=1 + else + STATE=0 + fi +} + +case $SMF_METHOD in +"start") + #FIXME - This HAS to be == rsync-trigger program + PLUGIN_CMD="$(svcprop -p plugin/trigger_command $SMF_FMRI)" + if [ $? -ne 0 ] ; then + echo "Unable to obtain plugin invocation command" + exit $SMF_EXIT_ERR_CONFIG + fi + + if [ ! -x $PLUGIN_CMD ] ; then + echo "\"$PLUGIN_CMD\" is not an executable path" + exit $SMF_EXIT_ERR_CONFIG + fi + add_rsync_cronjob + ;; +"stop") + remove_rsync_cronjob + ;; +*) + echo "Command line invocation of ${0} unsupported." + echo "This script is intended for smf(5) invocation only" + exit $SMF_EXIT_ERR_NOSMF + ;; +esac +exit $SMF_EXIT_OK diff --git a/po/.intltool-merge-cache b/po/.intltool-merge-cache new file mode 100644 index 0000000..3bb02ae --- /dev/null +++ b/po/.intltool-merge-cache @@ -0,0 +1,543 @@ +caSummaryResumidScanning for older versionsPemindaian untuk versi lamacsConfigure the system to take automatic snapshots of your dataNakonfigurujte si systém tak, aby vytvářel automatické snímky VaÅ¡ich datzh_TW19/01/0919/01/09zh_HKSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.[立即擷取快照] 需要 2 個引數: +- 要進行快照擷取的目錄的路徑。 +- 與此目錄對應的 zfs 檔案系統。zh_CNA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +已创建了一个名为 %(valid_name)s 的 zfs 文件 +系统 %(zfs_fs)s +的快照。 +zh_CNThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 该快照管理器服务需要管理权限才能运行。没有为您指定必要的管理权限。 + +请咨询您的系统管理员 itTime Slider File Version ExplorerExplorer versione del file Time SliderjaSome snapshots could not be readいくつかのスナップショットを読み取れませんでしたitSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Per l'esecuzione dell’eliminazione delle istantanee sono necessari privilegi amministrativi. Non si dispone di privilegi amministrativi necessari. + +Rivolgersi all'amministratore di sistema. idTime Slider ManagerPengendali Time Sliderzh_CNError错误pt_BRConfigure the system to take automatic snapshots of your dataConfigurar o sistema para realizar instantâneos automáticos dos dadoszh_TWLegacy原來的plInvalid characters in snapshot nameNiedozwolone znaki w nazwie migawkiidCreation TimeWaktu PembuatancaTime Slider: Low Space WarningTime Slider: Advertència poc espaicsEmergency: '%s' is almost full!Stav nouze: '%s' je skoro plný!pl_All_WszystkiedeSome snapshots could not be readEinige Schnappschüsse konnten nicht gelesen werdenpl1 snapshot will be deleted.1 migawka będzie usuniętadeAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +Folgende Zeichen sind für den Schnappschussnamen zulässig: +[a-z][A-Z][0-9][-_.: +Alle ungültigen Zeichen werden entfernt +csDailyDennědewith the name :mit dem Namen:esThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.El servicio de administrador de instantáneas ha detectado un problema y se ha desactivado hasta que se solucione el problema. + +Para obtener más información, consulte la página de comando man svcs(1).arInvalid arguments count.فشل بعد الحجج huMount PointFelcsatolási pontidD_eselect AllD_eselect Allpt_BR19/01/0919/01/09koTake a snapshot now지금 스냅샷 실행caFile System NameNom Sistema de FitxerscsSome snapshots could not be deletedNěkteré snímky nemohou být smazánykoSelect ZFS snapshots to delete from the list below.다음 목록에서 삭제할 ZFS 스냅샷을 선택하십시오.jaTime Slider backs up data regularly by taking timed ZFS Snapshotsタイムスライダは、時刻指定の ZFS スナップショットを作成することによって定期的にデータをバックアップしますpt_BRSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Efetuar instantâneo agora requer privilégios administrativos para ser executado. Os privilégios administrativos necessários não lhe foram atribuídos. + +Consulte o administrador de sistema jaSnapshot Nameスナップショット名zh_CNThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).文件系统 '%s' 的占满程度已超过 %s%%。 +作为一种紧急措施,时间滑块已销毁了它的所有备份。 +要修复此问题,请删除 '%s' 上所有不必要的文件,或增加磁盘空间(请参见 ZFS 文档)。jaSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 「今すぐスナップショット」を実行するには管理権限が必要です。必要な管理権限が割り当てられていません。 + +システム管理者に問い合わせてくださいarDailyيومي arSnapshot can not be deletedصور النظام لا يمكن حذفها deOpen the current version of the fileDie aktuelle Version der Datei öffnenjaAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +スナップショット名に許可されている文字は次のとおりです: +[a-z][A-Z][0-9][-_.: +無効な文字はすべて削除されます。 +caSelect ZFS snapshots to delete from the list below.Seleccioneu les instantànies ZFS a eliminar del següent llistat.idWeeklyMingguankoDelete Snapshots스냅샷 삭제esVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.El explorador de versiones necesita 2 argumentos: +- La ruta del directorio raíz de las instantáneas. +- El nombre del archivo que se va a explorar.caEnable Time SliderActivar Time SliderfrOpen the current version of the fileOuvrez la version actuelle du fichierzh_TWTake a snapshot of 擷取以下項目的快照 koInsufficient Priviliges권한 부족nlSnapshot created successfullyMomentopname gemaaktcasize : mida : ja_Delete Snapshots...スナップショットの削除(_D)...huWarning: '%s' is getting fullFigyelmeztetés: '%s' megteliknlRecommended for most usersAanbevolen voor de meeste gebruikerszh_HKThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.快照管理員服務似乎並未安裝於此系統上。 + +請參閱「svcs(1) 線上手冊」,以取得更多資訊。pt_BRInvalid characters in snapshot nameCaracteres inválidos no nome do instantâneocsScanning for older versionsSkenuji pro starší verzezh_TWEnable Time Slider啟用時間調整器ja_Allすべて(_A)deEmergency: '%s' is almost full!Notfall: '%s' ist fast voll!zh_CNSnapshot Name快照名称zh_HKAll全部plPress Delete to continue.Naciśnij Delete żeby kontynuować. esSnapshot manager service errorError del servicio de administrador de instantáneaspt_BRSnapshot manager service errorErro do serviço do gerenciador de instantâneoshuOlder VersionsRégebbi Verziókzh_TW%d snapshots will be deleted.將刪除 %d 個快照。pt_BRMount PointPonto de montagemhuDouble click to open the fileDupla kattintás a fájl megnyitásáhozca10 Mo10 DldeReduce backups when storage space usage exceeds:Sicherungen reduzieren, wenn Speicherplatznutzung Folgendes überschreitet:zh_HK1/4 Hourly每 1/4 小時jaOpen the current version of the fileファイルの現在のバージョンを開くdegtk-closegtk-closeplSuccessPowodzeniefrFile System NameNom de système de fichiersarLegacyإرث zh_CNSnapshot manager service error快照管理器服务错误esname :nombre:pt_BROlder VersionsVersões mais antigashu19/01/0919/01/09deDeleting snapshots...Schnappschüsse werden gelöscht...koEnable Time Slider시간 슬라이더 활성화koReduce backups when storage space usage exceeds:저장소 공간 사용량이 초과되는 경우 백업 줄이기:jaAdvanced Options詳細オプションjaTime SliderタイムスライダarExit when done. الخروج عن الإنتهاء zh_CNInsufficient Priviliges权限不足csMount PointMísto připojenícaUrgent: '%s' is almost full!Urgent: '%s' gairebé està ple!idDelete SnapshotsMenghapus SnapshotsplErrorBłądpt_BRSome snapshots could not be readAlguns instantâneos não podem ser lidoszh_CNPress Delete to continue.按“删除”以继续。deCurrent VersionAktuelle VersionarAdvanced Optionsخيارات إضافية nl%d snapshots will be deleted.%d momentopnamen zullen worden verwijdertplLegacyLegacyid%d snapshots will be deleted.%d snapshots akan dihapusdefilenameDateinamezh_HKC_ustom:自訂(_U):koSuccess성공idTime SliderTime Sliderzh_HKThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).檔案系統:「%s」已超過 %s%%。 +時間調整器所採取的緊急措施是銷毀其所有的備份。 +若要修正此問題,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文件)。huwith the name :névvel :zh_CNThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.由于相关性问题,快照管理器服务已被置于脱机。已发现以下相关性问题: + +%s + +有关这些相关性问题的更多信息,请从命令提示符下运行 "svcs -xv"。frA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +Un instantané d'un système de fichiers zfs %(zfs_fs)s +nommé %(valid_name)s +a été créé. +koSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 지금 스냅샷 기능을 실행하려면 관리 권한이 필요합니다. 필요한 관리 권한이 할당되지 않았습니다. + +시스템 관리자에게 문의하십시오. plname :nazwa:jaThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.このシステムにはスナップショットマネージャーサービスがインストールされていないようです。 + +詳細は、svcs(1) のマニュアルページを参照してください。ja%d snapshots will be deleted.%d 個のスナップショットが削除されます。pt_BRInvalid arguments count.Total de argumentos inválidos.esInvalid arguments count.Recuento de argumentos no válidos.zh_HKFor advanced users適用於進階使用者nlOlder VersionsOudere versiescsExit when done. Ukončit po dokončení. koEmergency: '%s' is almost full!긴급: '%s'이(가) 거의 찼습니다!itC_ustom:Personali_zzato:zh_HKA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +已建立一個名為 %(valid_name)s 的 + zfs 檔案系統 %(zfs_fs)s +的快照。 +idReduce backups when storage space usage exceeds:Mengurangi ruang penyimpanan backup bila melebihi kapasitas :esDouble click to open the fileHaga doble clic para abrir este archivopt_BRSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator A exclusão de instantâneos requer privilégios administrativos para ser executada. Os privilégios administrativos necessários não lhe foram atribuídos. + +Consulte o administrador de sistema zh_TWSnapshot Name快照名稱jaDouble click to open the fileダブルクリックしてファイルを開くdeAllAllezh_CNlast modified : 上次修改时间: arDeleting snapshots...حذف صور النظام...csLegacyOdkazdeMonthlyMonatlichkoWarning: '%s' is getting full경고: '%s'이(가) 꽉 찼습니다.zh_CNOpen the current version of the file打开文件的当前版本jalast modified : 最終更新日付: nlwith the name :met de naam :plAllWszystkieplsize : rozmiar: frName:Nom :zh_HKAdvanced Options進階選項esSuccessCorrectofrSnapshot manager service dependency errorErreur de dépendance du service de gestionnaire d'instantanésdeFile System NameDateisystemnamenlConfigure the system to take automatic snapshots of your dataConfigureer het systeem om automatische momentopnamen van je gegevens te nemenzh_CNLegacy传统plSnapshot created successfullyMigawka została utworzonazh_CNInvalid arguments count.参数计数无效。frThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.Le service de gestionnaire d'instantanés ne semble pas être installé sur ce système. + +Pour de plus amples informations, reportez-vous à la page de manuel svcs(1).pt_BRSelectSelecionezh_TWC_ustom:自訂(_U):arEnable Time Sliderتفعيل شريط تمرير الوقتnl_Delete Snapshots..._Verwijder momentopnames...zh_CNTake a snapshot of 捕获以下项的快照 plTime Slider File Version ExplorerTime Slider File Version ExplorerdeSummaryZusammenfassungidTake a snapshot nowMengambil snapshot sekarangzh_HKCreation Time建立時間pt_BREmergency: '%s' is almost full!Emergência: '%s' está quase cheio!koThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).파일 시스템 '%s'이(가) 총 용량의 %s%%을(를) 초과했습니다. 해결 조치로시간 슬라이더가 일부 백업을 삭제했으며, 용량이 계속 줄어들 때 좀 더 삭제할 예정이며 ê²°êµ­ 모두 삭제합니다. +이러한 문제가 다시 발생하지 않도록 '%s'에서 불필요한 파일을 삭제하거나 디스크 공간을 추가합니다(ZFS 설명서 참조).pt_BRSnapshot can not be deletedO instantâneo não pode ser excluídoplWeeklyTygodniowocsReduce backups when storage space usage exceeds:ZmenÅ¡it zálohy pokud úložný prostor překročí:esEnable Time SliderHabilitar deslizador de tiempopt_BRPress Delete to continue.Pressione Delete para continuar.jaFor advanced users上級ユーザー向けar1 snapshot will be deleted.1 صورة نظام سوف تحذف pt_BRExit when done. Sair ao concluir. esDeleting SnapshotsEliminación de instantáneasjaThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).ファイルシステム: '%s' が総容量の %s%% を超えました。 +緊急の手段として、タイムスライダはすべてのバックアップを破棄しました。 +この問題を解決するために、'%s' 上の不必要なファイルをすべて削除するか、またはディスク容量を追加してください (ZFS のマニュアルを参照)。esDailyCada díaarHintملحوظة frSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Pour supprimer des instantanés, vous devez posséder des privilèges administratifs. Vous ne possédez pas les privilèges administratifs requis. + +Consultez votre administrateur système frThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).Le système de fichiers %s est rempli à plus de %s %%. +Par mesure d'urgence, le curseur temporel a détruit toutes ses sauvegardes. +Pour résoudre ce problème, supprimez tout fichier non indispensable sur %s ou ajoutez de l'espace disque (voir la documentation ZFS).koRecommended for most users대부분의 사용자에게 권장itEmergency: '%s' is almost full!Attenzione: '%s' è quasi pieno.csname :jméno :jaEmergency: '%s' is almost full!緊急: '%s' がほぼいっぱいです。huDeleting snapshots...Pillanatképek törlése...pt_BRSome snapshots could not be deletedAlguns instantâneos não podem ser excluídosplReduce backups when storage space usage exceeds:Zmniejsz liczbę backupów kiedy przestrzeń przechowywania przekroczy:zh_HKInsufficient Priviliges權限不足arErrorخطأ csPress Delete to continue.Stiskněte Smazat pro pokračování.huDetails...Részletek...plDailyDzienniezh_TWFile System:檔案系統:esTake a snapshot nowTomar una instantánea ahoranlTime SliderTijd glijderitAllTuttofrLegacyHéritédeSnapshot created successfullySchnappschuss wurde erfolgreich erstelltesSnapshot can not be deletedLa instantánea no se puede eliminardeName:Name:idAllSemuaesCreation TimeMomento de creaciónde%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s hat einen oder mehrere abhängige Klone und wird nicht gelöscht. Um diesen Schnappschuss zu löschen, löschen Sie zunächst alle Datensätze und Schnappschüsse, die aus diesem Schnappschuss geklont wurden.zh_CNcompare比较frConfigure the system to take automatic snapshots of your dataConfigurer le système de manière à prendre des instantanés automatiques des donnéesesMonthlyCada mesarThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).The file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).koDaily매일ko1/4 Hourly매25분itSnapshot NameNome istantaneakoPress Delete to continue.계속하려면 삭제를 누르십시오.idSelectPilihde_All_AllecaReduce backups when storage space usage exceeds:Reduir les còpies de seguretat quan l'espai d'emmagatzemament excedeixi:plSome snapshots could not be readNie można odczytać niektórych migawekzh_HKSuccess成功deCreation TimeErstellungszeitfrTake a snapshot of Prenez un instantané de huInvalid characters in snapshot nameÉrvénytelen karakterek a pillanatkép nevébenitVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.L’explorer della versione richiede 2 argomenti : +- La path della directory radice dell’istantanea . +- Il nome file da ricercare.zh_TWOlder Versions較舊版本itUrgent: '%s' is almost full!Avviso: '%s' è quasi pieno.zh_HKSnapshot manager service error快照管理員服務錯誤kocompare비교pt_BR10 Mo10 ModeHourlyStündlichnlSuccessSucceshuAllMindfr_Delete Snapshots...Supprimer _des instantanés...idSnapshot created successfullySukses membuat snapshotkoA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +zfs 파일 시스템 %(zfs_fs)s의 스냅샷 +%(valid_name)s이(가) +생성되었습니다. +zh_TWA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +已建立一個名為 %(valid_name)s 的 + zfs 檔案系統 %(zfs_fs)s +的快照。 +zh_HKTake a snapshot now立即擷取快照zh_HKsize : 大小: csD_eselect AllV_yřadit vÅ¡ecsOlder VersionsStarší verzept_BRUrgent: '%s' is almost full!Urgente: '%s' está quase cheio!huSnapshot manager service dependency errorPillanatkép kezelő szolgáltatás függőségi hibacsDetails...Podrobnosti...zh_TWSnapshot can not be deleted無法刪除快照pt_BRError occured while creating the snapshotErro ocorrido durante a criação do instantâneoitTime Slider backs up data regularly by taking timed ZFS SnapshotsTime Slider consente di eseguire regolarmente backup dei dati mediante l'acquisizione di istantanee ZFS a intervalli regolarizh_CN%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s 具有一个或多个相关克隆,不会被删除。要删除此快照,请首先删除从此快照克隆的所有数据集和快照。plSelect ZFS snapshots to delete from the list below.Wybierz migawki ZFS do usunięcia z listy poniżej.plTime Slider backs up data regularly by taking timed ZFS SnapshotsTime Slider regularnie robi kopię zapasową danych poprzez migawkę ZFSnlFile Systems To Back UpBestand systemen voor backupplCreation TimeCzas utworzeniakoTime Slider backs up data regularly by taking timed ZFS Snapshots시간 슬라이더는 시간이 지정된 ZFS 스냅샷을 가져와 데이터를 정기적으로 백업합니다.kosize : 크기: huTime Slider backs up data regularly by taking timed ZFS SnapshotsTime Slider rendszeresen végez mentéseket időzített ZFS pillanatképek készítésével.csError occured while creating the snapshotVyskytla se chyba při vytváření snímkuit_Delete Snapshots..._Elimina istantanee…arTime Slider backs up data regularly by taking timed ZFS Snapshotsشريط تمرير الوقت يقوم بنسخ إحتياطي للبيانات بشكل منتظم plSummaryPodsumowaniezh_CNof file system capacity关于文件系统容量zh_TWof file system capacity檔案系統容量nlTime Slider: Low Space WarningTime Slider: Low Space Warningcs%d snapshots will be deleted.%d snímky budou smazány.idFile System:File Sistem:itInvalid characters in snapshot nameCaratteri invalidi nel nome dell’istantaneaidMount PointMount Pointitcompareconfrontadeof file system capacityder Dateisystemkapazitätzh_TWThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).檔案系統:「%s」已超過 %s%%。 +時間調整器所採取的緊急措施是銷毀其所有的備份。 +若要修正此問題,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文件)。huScanning snapshots...Pillanatképek keresése...nl10 Mo10 MOzh_CNEmergency: '%s' is full!紧急:'%s' 已满!pt_BR_All_Todosja10 Mo10 Mozh_TWEmergency: '%s' is almost full!緊急:「%s」幾乎已滿!pt_BRThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator O serviço do gerenciador de instantâneos requer privilégios administrativos para ser executado. Os privilégios administrativos necessários não lhe foram atribuídos. + +Consulte o administrador de sistema arD_eselect AllD_eselect AllcaHourlyCada horajaSelect選択frInsufficient PriviligesPrivilèges insuffisantsitD_eselect AllD_eseleziona tuttozh_CNDeleting Snapshots正在删除快照idDetails...Rincian...zh_TWDeleting snapshots...正在刪除快照...huFor advanced usersTapasztalt felhasználóknakzh_TWThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 快照管理員服務需要有管理權限才能執行。您尚未被指定必要的管理權限。 + +請洽詢您的系統管理員 idSuccessSuksesdeFile Systems To Back UpDateisystem zur Sicherungzh_CNCurrent Version当前版本zh_HKError occured while creating the snapshot建立快照時發生錯誤plSome snapshots could not be deletedNie można usunąć niektórych migawekitSnapshot manager service errorErrore del servizio di gestione delle istantaneedeError occured while creating the snapshotFehler beim Erstellen des SchnappschussescaEmergency: '%s' is full!Emergència: '%s' està ple!huTime Slider: Low Space WarningTime Slider: Kevés Tárhely FigyelmeztetésidPress Delete to continue.Tekan Delete untuk melanjutkan.pt_BRThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.O serviço do gerenciador de instantâneos encontrou um problema e ficará desativado até que o problema seja corrigido. + +Para obter mais informações, consulte a página do manual svcs(1).idDeleting snapshots...Menghapus snapshots...idDouble click to open the fileKlik dua kali untuk membuka filezh_TWMonthly每月zh_CNAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +快照名称中允许使用的字符为: +[a-z][A-Z][0-9][-_.: +所有无效字符都会被删除 +de1/4 HourlyViertelstündlichitScanning for older versionsRicerca delle versioni precedenti in corso...huOpen the current version of the fileAz aktuális fájl verzió megnyitásajaUrgent: '%s' is almost full!緊急: '%s' がほぼいっぱいです。esLegacyAntiguokoThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).파일 시스템 '%s'이(가) %s%% 이상으로 꽉 찼습니다. +긴급 조치로 시간 슬라이더가 모든 백업을 삭제했습니다 +이 문제를 해결하려면 '%s'에서 불필요한 파일을 삭제하거나 디스크 공간을 추가하십시오(ZFS 설명서 참조).zh_CNInvalid characters in snapshot name快照名称中存在无效字符idExit when done. Keluar ketika selesai zh_HKof file system capacity檔案系統容量zh_CNSnapshot can not be deleted无法删除快照essize : tamaño: hulast modified : Utoljára módosítva : jaThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.スナップショットマネージャーサービスが問題を検出し、この問題が解決されるまで無効になっています。 + +詳細は、svcs(1) のマニュアルページを参照してください。deThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).Das Dateisystem: '%s', ist über %s%% voll. +Der Zeit-Schieberegler hat im Zuge einer Notfallmaßnahme alle Sicherungsdateien gelöscht. +Um dieses Problem zu beheben, löschen Sie alle nicht benötigten Dateien auf '%s', oder fügen Sie Speicherplatz hinzu (siehe ZFS-Dokumentation).pt_BRCreation TimeHora de criaçãocalast modified : última modificació : zh_TW10 Mo10 MojaDelete Snapshotsスナップショットの削除idThe file system: '%s', is over %s%% full.file sistem: '%s', melebihi %s%% penuh.frThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.Le service de gestionnaire d'instantané a été mis hors ligne en raison d'un problème de dépendance. Les problèmes de dépendance suivants ont été détectés : + +%s + +Exécutez "svcs -xv" à partir d'une invite de commande pour plus d'informations sur ces problèmes de dépendance.ar_Delete Snapshots..._حذف صور النظام ....plSnapshot can not be deletedMigawka nie może byś usuniętacaFile Systems To Back UpSistema de fitxers a fer Còpia de seguretatfrwith the name :nommé :pl10 Mo10 Mokoname :이름:esDeleting snapshots...Eliminando instantáneas...csTake a snapshot nowPořídit snímek nyníarInsufficient Priviligesالإمتيازات غير كافية koAll모두csSnapshot manager service dependency errorChyba v závislostech ve službě Správce snímkůplOlder VersionsStarsza Wersjazh_HKReduce backups when storage space usage exceeds:當儲存空間使用量超過下列大小時減少備份:esFile System NameNombre del sistema de archivositof file system capacitydella capacità del file systemko_All모두(_A)pt_BRDouble click to open the fileClicar duas vezes para abrir o arquivozh_HKConfigure the system to take automatic snapshots of your data將系統配置為自動對資料執行快照caPress Delete to continue.Premeu a Eliminar per a continuar.huFile System:Fájlrendszer:idname :nama :zh_TWScanning for older versions掃描較舊版本arSelectإختر itFor advanced usersPer utenti avanzatizh_TWWeekly每週deTake a snapshot of Einen Schnappschuss nehmen von esSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator La eliminación de la instantánea necesita privilegios administrativos. No se le ha asignado los correspondientes privilegios administrativos. + +Consulte al administrador del sistemacsInsufficient PriviligesNedostatečná právafrScanning snapshots...Analyse des instantanés...jaSome snapshots could not be deletedいくつかのスナップショットを削除できませんでしたzh_TWName:名稱:itFile System NameNome file systemcsName:Jméno:nlSnapshot manager service dependency errorMomentopname manager service afhankelijkheid foutitMonthlyMensilejaThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.依存関係の問題が発生したために、スナップショットマネージャーサービスがオフラインになりました。次の依存関係の問題が検出されました: + +%s + +これらの依存関係の問題の詳細を表示するには、コマンドプロンプトから "svcs -xv" を実行してください。pt_BRReduce backups when storage space usage exceeds:Reduzir os backups quando o espaço de armazenamento for excedido:itlast modified : ultima modifica : jacompare比較esScanning for older versionsBuscando versiones anterioresdeRecommended for most usersEmpfohlen für die meisten BenutzeresSnapshot created successfullyLa instantánea se creó correctamenteplSnapshot NameNazwa migawkiitInsufficient PriviligesPrivilegi insufficientipt_BRSnapshot manager service dependency errorErro de dependência do serviço do gerenciador de instantâneosesThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).El sistema de archivos: '%s', ha sobrepasado un %s%% de su capacidad total. Como solución, el deslizador de tiempo ha destruido algunas copias de seguridad, y destruirá más, posiblemente todas, conforme siga disminuyendo la capacidad. +Para impedir que se repita este problema, elimine todos los archivos innecesarios de '%s' o agregue espacio al disco (consulte la documentación de ZFS).csDouble click to open the fileDvojitým kliknutím na soubor tento soubor otevřetecaDeleting snapshots...Eliminant Instantànies...esfilenamenombre_archivofrSelectSélectionnerjaWeekly毎週caLegacyLlegatesSummaryResumenzh_HKThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 快照管理員服務需要有管理權限才能執行。您尚未被指定必要的管理權限。 + +請洽詢您的系統管理員 csTime Slider ManagerSprávce pro Time SlidercsSnapshot NameJméno snímkukoHourly매시간arConfiguring Time Slider...إعدادات شريط التوقيت...zh_HKSnapshot created successfully已成功建立快照huAdvanced OptionsSzakértői beállításokcaC_ustom:_Personalitzat:huWeeklyHetikoSummary요약koCreation Time생성 시간csSummaryShrnutízh_TWReduce backups when storage space usage exceeds:當儲存空間使用量超過下列大小時減少備份:jaOlder Versions旧バージョンplSnapshot manager service dependency errorBłąd usługi menadżera migawekkoDouble click to open the file파일을 열려면 두 번 누릅니다.zh_HK%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s 具有一個或多個附屬複製,不會被刪除。要刪除此快照,請先刪除從此快照複製的所有資料集和快照。pl%d snapshots will be deleted.%d migawki zostaną usuniętehuUrgent: '%s' is almost full!Sürgős: '%s' majdnem megtelt!plDetails...Details...huTime Slider File Version ExplorerTime Slider Fájl Verzió IntézőhuTime SliderTime SliderarDouble click to open the fileإضغط مرتين لفتح الملف plMount PointPunkt montowaniacaSnapshot NameNom de la instantàniacaWeeklySetmanalfrPress Delete to continue.Cliquez sur Supprimer pour continuer.nlgtk-closegtk-sluitenfrVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.L'explorateur de versions nécessite 2 arguments : +- Chemin d'accès au répertoire d'instantanés racine. +- Nom de fichier à explorer.itSome snapshots could not be readImpossibile leggere alcune istantaneeitLegacyLegacyarSnapshot Nameإسم صورة النظام plHourlyCo godzinęitDeleting SnapshotsEliminazione delle istantaneeesTime Slider backs up data regularly by taking timed ZFS SnapshotsEl deslizador de tiempo hace copias de seguridad de datos con regularidad tomando instantáneas de ZFS programadasnlCurrent VersionHuidige versiezh_HKTime Slider File Version Explorer時間滑動軸檔案版本檔案總管ko_Delete Snapshots...스냅샷 삭제(_D)...it1/4 HourlyOgni quarto d’oraca19/01/0919/01/09deDelete SnapshotsSchnappschüsse löschencaOlder VersionsVersions AnteriorskoFor advanced users고급 사용자용itTime SliderTime SliderkoSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.지금 스냅샷 기능에는 다음과 같은 2개의 인수가 필요합니다. +- 스냅샷될 디렉토리의 경로 +- 이 디렉토리에 해당하는 zfs 파일 시스템nlInvalid arguments count.Ongeldige argumenten aantal.itInvalid arguments count.Calcolo degli argomenti non valido.huDailyNapijaSnapshot can not be deletedスナップショットを削除できませんitCreation TimeTempo di creazioneitCurrent VersionVersione correntezh_CN1/4 Hourly每 1/4 小时esThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).El sistema de archivos: '%s', está %s%% por encima. +Como medida de emergencia, el deslizador de tiempo ha destruido todas sus copias de seguridad. +Para solventar este problema, elimine todos los archivos innecesarios de '%s' o agregue espacio al disco (consulte la documentación de ZFS).zh_TWSelect ZFS snapshots to delete from the list below.請從以下清單中選取要刪除的 ZFS 快照。kowith the name :이름:jaAllすべてcsTime Slider File Version ExplorerTime Slider - Průzkumník verzí zh_CNThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.该快照管理器服务出现了问题,并已被禁用,直至问题得以修复。 + +有关更多信息,请参见 svcs(1) 手册页。zh_CNSummary摘要frThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Pour exécuter le service de gestionnaire d'instantanés, vous devez posséder des privilèges administratifs. Vous ne possédez pas les privilèges administratifs requis. + +Consultez votre administrateur système koEmergency: '%s' is full!긴급: '%s'이(가) 꽉 찼습니다!koUrgent: '%s' is almost full!중요: '%s'이(가) 거의 찼습니다!huSnapshot created successfullyPillanatkép sikeresen elkészítvezh_HKCurrent Version目前版本frSummaryRésumékoThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.스냅샷 관리자 서비스가 종속성 문제로 인해 오프라인 상태로 설정되었습니다. 다음과 같은 종속성 문제가 발견되었습니다. + +%s + +이러한 종속성 문제에 대한 자세한 내용을 보려면 명령 프롬프트에서 "svcs -xv"를 실행하십시오.zh_CNsize : 大小: idTime Slider File Version ExplorerTime Slider File Version ExploreridInvalid characters in snapshot nameInvalid karakter pada nama snapshotdeWeeklyWöchentlichplDeleting SnapshotsUsuwanie migawkiesSome snapshots could not be deletedNo se ha podido eliminar algunas instantáneasjaTake a snapshot now今すぐスナップショットを取得するde_Delete Snapshots..._Schnappschüsse löschen...arDelete Snapshotsحذف صور النظام huFile System NameFájlrendszer NevecaSnapshot manager service errorS'ha produït un error al servei de gestió de instantàniesfrAllToutidcomparebandingkanidInsufficient PriviligesHak akses tidak mencukupihuSnapshot NamePillanatkép neveitThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).Spazio del file system '%s' occupato oltre il %s%%. +Come misura di emergenza, Time Slider ha eliminato tutti i backup eseguiti. +Per risolvere il problema, eliminare i file non necessari in '%s' oppure aggiungere spazio nel disco (vedere la documentazione di ZFS).huConfigure the system to take automatic snapshots of your dataA rendszer beállítása arra, hogy automatikusan mentse az Ön adataitcaMount PointPunt de MuntatgehuFile Systems To Back UpFájl Rendszer Mentésejaof file system capacityファイルシステム容量caAllTotsplRecommended for most usersRekomendowane dla większości użytkownikówidErrorErroridOpen the current version of the fileBuka file dengan versi saat iniesFor advanced usersPara usuarios avanzadoszh_CNTime Slider backs up data regularly by taking timed ZFS Snapshots时间滑块通过定时拍摄 ZFS 快照来对数据进行定期备份zh_HKWeekly每週fr19/01/0919/01/09plUrgent: '%s' is almost full!Pilne: '%s' jest prawie zapełniony!zh_HKlast modified : 上次修改時間: zh_CNDeleting snapshots...正在删除快照...plC_ustom:C_ustom:frsize : taille : jaReduce backups when storage space usage exceeds:ストレージスペースの使用量が超過したらバックアップを削減する:zh_HKwith the name :名稱為:decomparevergleichenplAdvanced OptionsZaawansowane opcjecsSelectVybratnlSummarySamenvattingitTake a snapshot of Crea un’istantanea di cslast modified : Naposledy změněn : nlError occured while creating the snapshotFout opgetreden tijdens het maken van de momentopnamept_BRDelete SnapshotsExcluir instantâneoshuSome snapshots could not be readNéhány pillanatkép nem olvashatóidOlder VersionsVersi Lamakoof file system capacity- 파일 시스템 용량huThe file system: '%s', exceeded %s%% of its total capacityA fájlrendszer: '%s' túllépte teljes kapacitásának %s%%-átpt_BRTime Slider backs up data regularly by taking timed ZFS SnapshotsA barra deslizante de tempo faz backup de dados regularmente realizando instantâneos programados do ZFSarSome snapshots could not be deletedبعض صور النظام لايمكن أن تحذف csWeeklyTýdnězh_TWTime Slider時間滑動軸jaLegacy旧バージョンzh_HKWarning: '%s' is getting full警告:「%s」快要滿了arPress Delete to continue.إضغط على حذف للإستمرار zh_TWSummary摘要esError occured while creating the snapshotError al crear la instantáneadeSnapshot NameSchnappschussnamearHourlyساعي husize : méret : itDeleting snapshots...Eliminazione delle istantanee in corso…ja19/01/0919/01/09caTime Slider ManagerGestor de Time SlidercaConfigure the system to take automatic snapshots of your dataConfigurar el sistema per a fer instantànies automàtiques de les vostres dadeszh_TWcompare對比caScanning snapshots...Escanejant instantànies....zh_CNSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 需要有管理权限才能立即进行快照。没有为您指定必要的管理权限。 + +请咨询您的系统管理员 pt_BRof file system capacityda capacidade do sistema de arquivoscaRecommended for most usersRecomanat per a la majoria d'usuarishuName:Név:esSelect ZFS snapshots to delete from the list below.Seleccione las instantáneas ZFS que desea eliminar de la siguiente lista.frname :nom :frCurrent VersionVersion actuellecsCurrent VersionSoučasná verzezh_HK1 snapshot will be deleted.將刪除 1 個快照。deUrgent: '%s' is almost full!Dringend: '%s' ist fast voll!esAdvanced OptionsOpciones avanzadascaDelete SnapshotsEliminar instantàniescsDelete SnapshotsSmazat snímkypt_BRWeeklySemanalmentezh_HKTime Slider時間滑動軸jaDaily毎日csHintNápovědazh_CNSelect选择itfilenamenome filenlThe file system: '%s', is over %s%% full.The file system: '%s', is over %s%% full.itOlder VersionsVersioni precedentiarSnapshot manager service errorخطأ خدمة إدارة صورة النظام zh_TWFor advanced users適用於進階使用者cafilenamenomfitxeridAdvanced OptionsPilihan LanjutanplConfigure the system to take automatic snapshots of your dataSkonfiguruj system do automatycznego wykonywania migawek twoich danych frScanning for older versionsRecherche d'anciennes versionscsAllVÅ¡ecaFile System:Sistema de Fitxers:esPress Delete to continue.Pulse Suprimir para continuar.zh_HKPress Delete to continue.請按下 [刪除] 繼續。deSelect ZFS snapshots to delete from the list below.Wählen Sie aus der nachstehenden Liste die zu löschenden ZFS-Schnappschüsse aus.koName:이름:hucompareösszehasonlításfrSome snapshots could not be deletedCertains instantanés n'ont pas pu être supprimésarDeleting Snapshotsحذف صور النظام caErrorErrorzh_CNEmergency: '%s' is almost full!紧急:'%s' 几乎已满!koDeleting snapshots...스냅샷 삭제 중...zh_CNSnapshot manager service dependency error快照管理器服务相关性错误arlast modified : أخر تعديل : zh_CNC_ustom:自定义(_U):pt_BRThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.O serviço do gerenciador de instantâneos parece não estar instalado neste sistema. + +Para obter mais informações, consulte a página do manual svcs(1).pt_BRCurrent VersionVersão atualjaDeleting snapshots...スナップショットを削除しています...de%d snapshots will be deleted.%d Schnappschüsse werden gelöscht.arwith the name :بإسم :zh_TW%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s 具有一個或多個附屬複製,不會被刪除。要刪除此快照,請先刪除從此快照複製的所有資料集和快照。caTime Slider backs up data regularly by taking timed ZFS SnapshotsTime Slider fa còpies de seguretat regularment prenent Instantànies ZFS programadeszh_HKScanning for older versions掃描較舊版本zh_CNSome snapshots could not be read无法读取某些快照arSnapshot created successfullyصورة النظام قد أنشأت بنجاح frThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).Le système de fichiers %s dépasse %s %% de sa capacité totale. Pour résoudre cette situation, le curseur temporel a détruit certaines sauvegardes, en détruira d'autres et finira par les détruire toutes, si la capacité continue de diminue. +Pour éviter que cela se reproduise, supprimez tout fichier non indispensable sur %s ou ajoutez de l'espace disque (voir la documentation ZFS).caName:Nom:csSuccessÚspěchcs_All_VÅ¡ehufilenamefájlnévjaSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator スナップショットの削除を実行するには管理権限が必要です。必要な管理権限が割り当てられていません。 + +システム管理者に問い合わせてくださいcaof file system capacityde la capacitat del sistema de fitxerscaDailyDiàriacaMonthlyMensualcaSnapshot can not be deletedLa instantània no pot ser eliminadafrDouble click to open the fileCliquez deux fois sur le fichier pour l'ouvrir.caConfiguring Time Slider...Configurant Time Slider...nlD_eselect AllD_eselecteer allezh_CNDaily每天huInsufficient PriviligesNem megfelelő jogosultságokpt_BRsize : tamanho : frAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +Les caractères autorisés dans les noms d'instantané sont les suivants : +[a-z][A-Z][0-9][-_.: +Tous les caractères non autorisés seront supprimés +frRecommended for most usersRecommandé pour la plupart des utilisateursitReduce backups when storage space usage exceeds:Ridurre i backup quando lo spazio di memorizzazione utilizzato supera:arName:إسم : caWarning: '%s' is getting fullAvís: '%s' s'està emplenantitAdvanced OptionsOpzioni avanzatehuHintJavaslatcsHourlyKaždou hodinucsTime Slider: Low Space WarningTime Slider: Varování - málo místaplDeleting snapshots...Usuwanie migawek...koMonthly매달huC_ustom:_Egyedi:zh_TWOpen the current version of the file開啟目前版本的檔案pt_BRC_ustom:P_ersonalizado:deAdvanced OptionsErweiterte Optionenar%d snapshots will be deleted.%d الصورة سوف تمحى idwith the name :dengan nama :deExit when done. Beenden Sie die Anwendung, wenn Sie fertig sind. deC_ustom:B_enutzerdefiniert:plOpen the current version of the fileOtwórz bieżącą wersję plikupllast modified : ostatnia modyfikacja : idConfiguring Time Slider...Sedang mengkonfigurasi Time Slider...deSnapshot manager service errorSchnappschussverwaltungsdienst-FehlercaSnapshot created successfullyInstantània correctament creadaitOpen the current version of the fileApri la versione corrente del filecsSelect ZFS snapshots to delete from the list below.Vyberte ZFS snímek ze seznamu, který má být smazán.zh_TWSelect選取plwith the name :z nazwą:nlSome snapshots could not be deletedSommige momentopnames konden niet verwijdert wordenhuLegacyHagyományoskoTime Slider시간 슬라이더deSome snapshots could not be deletedEinige Schnappschüsse konnten nicht gelöscht werdenzh_HKDouble click to open the file連按兩下以開啟檔案pt_BRSnapshot NameNome do instantâneohuTime Slider ManagerTime Slider Vezérlőca_All_TotsitErrorErrorecs19/01/0919/01/09plInsufficient PriviligesNiewystarczające uprawnieniafr10 Mo10 MofrReduce backups when storage space usage exceeds:Réduire les sauvegardes lorsque l'espace de stockage utilisé dépasse :esInvalid characters in snapshot nameCaracteres no válidos en el nombre de la instantáneaarDetails...تفاصيل...deSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.„Schnappschuss jetzt“ erfordert 2 Argumente: +- Pfad des Verzeichnisses, von dem ein Schnappschuss genommen werden soll. +- Entsprechendes ZFS-Dateisystem für dieses Verzeichnis.nlname :name :deLegacyVeraltetzh_HKSome snapshots could not be read無法讀取某些快照plDouble click to open the fileKliknij dwa razy, żeby otworzyć plikzh_HKTime Slider Manager時間滑動軸管理員koSelect선택arConfigure the system to take automatic snapshots of your dataتاكيد النظام على أخذ صورة عن النظام عن البيانات cawith the name :amb el nom :frInvalid characters in snapshot nameLe nom de l'instantané contient des caractères non autorisésesInsufficient PriviligesLos privilegios no son suficientesidDeleting SnapshotsMenghapus Snapshotspt_BRlast modified : última modificação: itScanning snapshots...Scansione delle istantanee in corso…caThe file system: '%s', is over %s%% full.El sistema de fitxers: '%s', sobreexcedeix %s%%.huCreation TimeKészítés idejefrTime Slider File Version ExplorerExplorateur de versions du fichier de curseur temporelcsAdvanced OptionsRozšířené možnostiitSummaryRiepilogonlOpen the current version of the fileOpen de huidige versie van het bestandzh_CNName:名称:itname :nome :pt_BRThe file system: '%s', is over %s%% full. +As an emergency measure, Time Slider has destroyed all of its backups. +To fix this problem, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).O sistema de arquivos: '%s' está %s%% acima. +Como medida de emergência, a barra deslizante de tempo eliminou todos os backups. +Para corrigir este problema, exclua os arquivos desnecessários de '%s' ou adicione espaço em disco (consulte a documentação do ZFS).nlDeleting snapshots...Momentopnamen worden verwijdert...arReduce backups when storage space usage exceeds:تخفيض النسخة الإحتياطية عند تجاوز المساحة النخزينية argtk-closegtk-إغلاق plFile System NameNazwa systemu plikówzh_HKEnable Time Slider啟用時間調整器itAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +I caratteri ammessi per i nomi delle istantanee sono : +[a-z][A-Z][0-9][-_.: +Tutti i caratteri invalidi saranno rimossi +pt_BRFile Systems To Back UpSistemas de arquivos dos quais fazer backupzh_TWAdvanced Options進階選項zh_CND_eselect All取消全选(_E)plTake a snapshot of Zrób migawkę jaSuccess成功koSome snapshots could not be read일부 스냅샷을 읽을 수 없습니다.pt_BRA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +Um instantâneo do sistema de arquivos zfs %(zfs_fs)s +denominado %(valid_name)s +foi criado. +jagtk-closegtk-closeitTime Slider ManagerGestione Time Sliderzh_TWwith the name :名稱為:idCurrent VersionVersi Saat InifrTime Slider ManagerGestionnaire de curseur temporelzh_TWDeleting Snapshots正在刪除快照csSnapshot created successfullySnímek byl vytvořen úspěšněplExit when done. Wyjdź kiedy skończysz. itSome snapshots could not be deletedImpossibile eliminare alcune istantanee nlfilenamebestand naamzh_CNThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).文件系统 '%s' 已超过其总容量的 %s%%。作为一种补救措施,时间滑块已销毁了一些备份,并随着容量的不断减小,将销毁更多备份,最终销毁所有备份。 +要防止这种情况再次发生,请删除 '%s' 上所有不必要的文件,或增加磁盘空间(请参见 ZFS 文档)。csTime SliderTime Slidernl_All_AllearTime Slider File Version Explorerمتصفح ملف إصدار شريط تمرير الوقت arof file system capacityقدرة نظام ملفات النظام zh_HKSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 需要具有管理權限才能執行 [快照刪除] 操作。沒有為您指定所需的管理權限。 + +請諮詢系統管理員 de1 snapshot will be deleted.1 Schnappschuss wird gelöscht.delast modified : Letzte Änderung: caAdvanced OptionsOpcions avançadeskoSnapshot manager service error스냅샷 관리자 서비스 오류itDelete SnapshotsElimina istantaneezh_CN1 snapshot will be deleted.1 个快照将被删除。zh_CNSuccess成功koC_ustom:사용자 정의(_U):zh_CNSelect ZFS snapshots to delete from the list below.从以下列表中选择要删除的 ZFS 快照。hu%d snapshots will be deleted.%d pillanatkép törölve lesz.csInvalid arguments count.Neplatný počet argumentů.deThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.Der Schnappschussverwaltungsdienst ist von einem Problem betroffen und wurde bis zur Behebung des Problems deaktiviert. + +Weitere Informationen finden Sie in der Online-Dokumentation, svcs(1).pt_BRFor advanced usersPara usuários avançadosdeWarning: '%s' is getting fullWarnung: '%s' wird vollzh_CN19/01/0919/01/09deDeleting SnapshotsLöschen von SchnappschüssenarTake a snapshot nowخذ نسخة من صورة النظام الأن es1/4 HourlyCada cuarto de horafrSnapshot can not be deletedImpossible de supprimer l'instantanéplMonthlyMiesięczniekoSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 스냅샷 삭제 기능을 실행하려면 관리 권한이 필요합니다. 필요한 관리 권한이 할당되지 않았습니다. + +시스템 관리자에게 문의하십시오. deFile System:Dateisystem:pt_BRSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.Efetuar instantâneo agora requer 2 argumentos : +- O caminho do diretório do qual será efetuado o instantâneo. +- O sistema de arquivos zfs correspondente a este diretório.zh_TWThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).檔案系統:「%s」超過了其總容量的 %s%%。時間調整器所採取的補救措施是銷毀部分備份,並將會銷毀更多備份,而最終會銷毀所有備份,因為容量一直不斷減少。 +若要避免再次發生這種情形,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文件)。zh_CNScanning snapshots...正在扫描快照...pt_BR1 snapshot will be deleted.1 instantâneo será excluído.cssize : velikost : arSelect ZFS snapshots to delete from the list below.إختر ZFS صور النظام لحذف من اسفل القائمة ca1/4 HourlyCada 1/4 d'horaitDailyOgni giornocsInvalid characters in snapshot nameNeplatný znak v názvu snímkujaSnapshot manager service dependency errorスナップショットマネージャーサービスの依存関係エラーpt_BRAdvanced OptionsOpções avançadasar1/4 Hourly1/4 ساعي caCurrent VersionVersió ActualjaInvalid arguments count.引数の数が無効です。esUrgent: '%s' is almost full!Urgente: '%s' se ha llenado casi del todokolast modified : 마지막 수정: nl19/01/0919/01/09deSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator "Schnappschuss jetzt" erfordert Administratorrechte. Ihnen wurden nicht die nötigen Administratorrechte zugewiesen. + +Wenden Sie sich an Ihren Systemadministrator. zh_HKD_eselect All全部取消選取(_E)dePress Delete to continue.Klicken Sie auf „Löschen“, um fortzufahren.jaDeleting Snapshotsスナップショットを削除していますzh_HKAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +允許用於快照名稱的字元如下: +[a-z]、[A-Z]、[0-9]、[、-、_、.、: +將移除所有無效字元 +esSnapshot NameNombre de instantáneazh_TWSnapshot created successfully已成功建立快照nlHourlyIeder uurzh_HKFile Systems To Back Up要備份的檔案系統itTake a snapshot nowCrea un’istantaneaar_All_الكل nlDetails...Details...nllast modified : laatst bewerkt : arsize : الحجم : kogtk-closegtk-closezh_HKThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.由於相依性問題,已將快照管理員服務置為離線。發現以下相依性問題: + +%s + +請從指令提示符號下執行「svcs –xv」,以取得有關這些相依性問題的更多資訊。itExit when done. Chiudi al termine dell'operazione. itThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.Il servizio di gestione delle istantanee è stato disconnesso a causa di un problema di dipendenza. Sono stati riscontrati i seguenti problemi di dipendenza: + +%s + +Eseguire "svcs -xv" da un prompt di comando per ottenere maggiori informazioni su questi problemi di dipendenza.pt_BRInsufficient PriviligesPrivilégios insuficientescaInvalid arguments count.Nombre d'arguments incorrectes.zh_CNSnapshot created successfully已成功创建快照arTake a snapshot of خذ نسخة من صورة النظام frof file system capacityde la capacité du système de fichiersit%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s possiede uno o più cloni dipendenti e non verrà eliminata. Per eliminare questa istantanea, eliminare prima tutti i set di dati e le istantanee clonate da questa istantanea.nlInvalid characters in snapshot nameOngeldige tekens in momentopname naamidSnapshot manager service errorLayanan pengendali Snapshot errorcsRecommended for most usersDoporučeno pro větÅ¡inu uživatelůcaSelectSeleccionararSummary ملخص huTake a snapshot of Pillanatkép készítése a pl_Delete Snapshots..._Usuwanie Migawki...zh_TWInsufficient Priviliges權限不足pt_BRfilenamenome de arquivodeDailyTäglichdeDouble click to open the fileAuf die Datei doppelklicken, um sie zu öffnenkofilename파일 이름zh_TWSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.[立即擷取快照] 需要 2 個引數: +- 要進行快照擷取的目錄的路徑。 +- 與此目錄對應的 zfs 檔案系統。esTake a snapshot of Tomar una instantánea de frlast modified : dernière modification : arCurrent Versionالإصدار الحاليde19/01/0919/01/09deA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +Ein Schnappschuss des ZFS-Dateisystems %(zfs_fs)s +mit dem Namen %(valid_name)s +wurde erstellt. +nlTake a snapshot nowNeem nu een momentopnamezh_CNwith the name :名称为:frEmergency: '%s' is almost full!Urgence : %s est presque plein !zh_TWInvalid arguments count.引數計數無效。arUrgent: '%s' is almost full!عاجل : '%s' على الإغلب ممتلئ esSome snapshots could not be readNo se ha podido leer algunas instantáneasplCurrent Version Wersja bieżącadeTime SliderZeit-SchiebereglernlSnapshot can not be deletedMomentopname kan niet gewist wordenhu10 Mo10 megabájtdeSelectAuswählenidDailyHariannlEmergency: '%s' is almost full!Noodgeval: '% s' is bijna vol!caInsufficient PriviligesPrivilegis insuficientsdeScanning snapshots...Suche nach Schnappschüssen wird ausgeführt...frHourlyToutes les heuresesSelectSeleccionarcaEmergency: '%s' is almost full!Emergència: '%s' gairebé està ple!pt_BRAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +O caracteres permitidos em nomes de instantâneos são : +[a-z][A-Z][0-9][-_.: +Todos os caracteres inválidos serão removidos +plInvalid arguments count.Niepoprawna liczba argumentów.zh_TWSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 需要具有管理權限才能執行 [立即擷取快照] 操作。沒有為您指定所需的管理權限。 + +請諮詢系統管理員 zh_HK19/01/0919/01/09zh_CNgtk-closegtk-closearError occured while creating the snapshotالخطأ حدث عند إنشاء صورة النظام deThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Für die Ausführung des Schnappschussverwaltungsdienstes sind Administratorrechte erforderlich. Ihnen wurden nicht die nötigen Administratorrechte zugewiesen. + +Wenden Sie sich an den Systemadministrator zh_HKMonthly每月zh_TWUrgent: '%s' is almost full!緊要:「%s」幾乎已滿!zh_HK_Delete Snapshots...刪除快照(_D)...huSuccessEredménykoVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.버전 탐색기에 다음과 같은 2개의 인수가 필요합니다. +- 루트 스냅샷 디렉토리의 경로 +- 탐색할 파일 이름koExit when done. 완료 후 종료합니다. plTake a snapshot nowZrób migawkę terazjaSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.「今すぐスナップショット」には次の 2 つの引数が必要です: +- スナップショットを取得するディレクトリのパス。 +- このディレクトリに対応する zfs ファイルシステム。caError occured while creating the snapshotS'ha produït un error mentre s'estava creant la instantàniazh_HKOlder Versions較舊版本frgtk-closegtk-closedesize : Größe: zh_TWError錯誤deTime Slider File Version ExplorerTime Slider File Version Explorerzh_TWTime Slider File Version Explorer時間滑動軸檔案版本檔案總管ca_Delete Snapshots..._Eliminar Instantànies...deThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).Das Dateisystem: '%s', %s%% der Gesamtkapazität überschritten. Der Zeit-Schieberegler hat im Zuge einer Abhilfemaßnahme einige Sicherungsdateien gelöscht und wird weitere und letztlich alle Sicherungsdateien löschen, wenn die Kapazität weiter abnimmt. +Um zu verhindern, dass dieses Problem erneut auftritt, löschen Sie alle nicht benötigten Dateien auf '%s', oder fügen Sie Speicherplatz hinzu (siehe ZFS-Dokumentation).csFor advanced usersPro pokročilé uživatelezh_HKSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 需要具有管理權限才能執行 [立即擷取快照] 操作。沒有為您指定所需的管理權限。 + +請諮詢系統管理員 nlSome snapshots could not be readSommige momentopnames konden niet gelezen wordenfrWarning: '%s' is getting fullAvertissement : %s est presque pleinzh_CNVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.版本资源管理器需要 2 个参数: +- 根快照目录的路径。 +- 要浏览的文件名。koDeleting Snapshots스냅샷 삭제 중ko19/01/0919/01/09csThe file system: '%s', is over %s%% full.Souborový systém: %s', je z %s%% plný.esErrorErrorzh_HKThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).檔案系統:「%s」超過了其總容量的 %s%%。時間調整器所採取的補救措施是銷毀部分備份,並將會銷毀更多備份,而最終會銷毀所有備份,因為容量一直不斷減少。 +若要避免再次發生這種情形,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文件)。nlDouble click to open the fileDubbelklik om het bestand te openenzh_TWThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.快照管理員服務遇到了問題,而且在問題修正之前都處於停用狀態。 + +請參閱「svcs(1) 線上手冊」,以取得更多資訊。hu1/4 HourlyNegyed óránkéntijaTime Slider File Version ExplorerタイムスライダファイルバージョンエクスプローラnlTake a snapshot of Neem een momentopname van pt_BROpen the current version of the fileAbrir a versão atual do arquivojaD_eselect Allすべてを選択解除(_E)plfilenamenazwa plikupt_BRThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.O serviço do gerenciador de instantâneos foi colocado off-line devido a um problema de dependência. Foram encontrados os seguintes problemas de dependência: + +%s + +Execute "svcs -xv" de um prompt de comando para obter mais informações sobre estes problemas de dependência.csSnapshot manager service errorChyba ve službě Správce snímkůesA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +Una instantánea del sistema de archivos zfs %(zfs_fs)s +llamada %(valid_name)s +se ha creado. +huCurrent VersionJelenlegi Verzióid_Delete Snapshots..._Menghapus Snapshots...fr1/4 HourlyTous les quarts d'heureitThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.Il servizio di gestione delle istantanee non sembra essere installato nel sistema corrente. + +Per ulteriori informazioni consultare la pagina man svcs(1).koAdvanced Options고급 옵션jaInvalid characters in snapshot nameスナップショット名の文字が無効ですdeScanning for older versionsNach älteren Versionen suchennlErrorFoutes_Delete Snapshots..._Eliminar instantáneas...itError occured while creating the snapshotErrore durante la creazione dell’istantaneanlName:Naam:huD_eselect AllKijelölések megszüntetésezh_CNError occured while creating the snapshot创建快照时发生错误caname :nom :deThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.Der Schnappschussverwaltungsdienst ist offenbar nicht auf diesem System installiert. + +Weitere Informationen finden Sie in der Online-Dokumentation, svcs(1).plSnapshot manager service errorBłąd menadżera migawekesConfigure the system to take automatic snapshots of your dataConfigurar el sistema para tomar instantáneas de manera automática de los datosnlTime Slider File Version ExplorerTijd slijder bestand versie onverzoekerfr1 snapshot will be deleted.1 instantané sera supprimé.pt_BRFile System NameNome do sistema de arquivoszh_TWfilename檔案名稱zh_HKSelect ZFS snapshots to delete from the list below.請從以下清單中選取要刪除的 ZFS 快照。jaScanning snapshots...スナップショットをスキャンしています...arname :إسم : idgtk-closegtk-closejaThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator スナップショットマネージャーサービスを実行するには管理権限が必要です。必要な管理権限が割り当てられていません。 + +システム管理者に問い合わせてくださいidSome snapshots could not be readBeberapa snapshot tidak dapat dibacazh_TWWarning: '%s' is getting full警告:「%s」快要滿了zh_HKRecommended for most users建議大多數使用者採用itSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.Crea istantanea richiede 2 argomenti : +- La path della directory di cui deve essere creata l’istantanea. +- Il file system zfs corrispondente a questa directory.csScanning snapshots...Skenuji snímky...zh_TWlast modified : 上次修改時間: idSummaryHasilidlast modified : modifikasi terakhir : dename :Name:es19/01/0919/01/09plgtk-closegtk-zamknijesHourlyCada horapt_BRDailyDiariamentehuDeleting SnapshotsPillanatképek törléseid_All_Semuajafilenameファイル名idTime Slider: Low Space WarningTime Slider: Peringatan Tempat Penyimpanan Hampir PenuhnlDelete SnapshotsVerwijder momentopnamenzh_TWSome snapshots could not be read無法讀取某些快照koTime Slider Manager시간 슬라이더 관리자pt_BRTime Slider File Version ExplorerExplorador de versões de arquivos da barra deslizante de tempozh_HKDelete Snapshots刪除快照zh_TWSnapshot manager service dependency error快照管理員服務相依性錯誤zh_TW1/4 Hourly每 1/4 小時frDailyJournalièrearSome snapshots could not be readبعض صور النظام لايمكن أن تقرأ zh_HKTake a snapshot of 擷取以下項目的快照 idInvalid arguments count.Kesalahan argumen bertambahjaCurrent Version現在のバージョンfrFile Systems To Back UpSystèmes de fichiers à sauvegarderarFor advanced usersمن أجل المستخدمين المتقدمين itDouble click to open the fileFare doppio clic per aprire il filezh_CNUrgent: '%s' is almost full!紧急:'%s' 几乎已满!csErrorChybadeSnapshot can not be deletedSchnappschuss kann nicht gelöscht werdenesRecommended for most usersRecomendado para la mayoría de los usuarioszh_TWSome snapshots could not be deleted無法刪除某些快照plSelectWybierzjaSnapshot manager service errorスナップショットマネージャーサービスのエラーzh_TWDaily每天esScanning snapshots...Buscando instantáneas...csWarning: '%s' is getting fullUpozornění: '% s' se zaplňujenlHintHintzh_TWCurrent Version目前版本esOlder VersionsVersiones anterioreskoThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 스냅샷 관리자 서비스를 실행하려면 관리 권한이 필요합니다. 필요한관리 권한이 지정되지 않았습니다. + +시스템 관리자에게 문의하십시오. zh_HKOpen the current version of the file開啟目前版本的檔案arSnapshot manager service dependency errorخدمة إدارة صورة النظام تعتمد خطأ pt_BR1/4 HourlyCada 15 minutosarFile System Nameإسم ملف النظام esEmergency: '%s' is almost full!Emergencia: '%s' se ha llenado casi del todoidMonthlyBulanannlTime Slider ManagerTijd glijder beheerdercs10 MoPondělí 10zh_TWDouble click to open the file連按兩下以開啟檔案hugtk-closegtk-closekoTime Slider File Version Explorer시간 슬라이더 파일 버전 탐색기koFile Systems To Back Up백업할 파일 시스템idfilenamenama filept_BRgtk-closegtk-closezh_HKEmergency: '%s' is almost full!緊急:「%s」幾乎已滿!jaInsufficient Priviliges権限が不足していますarRecommended for most usersالمطلوب من كل المستخدمين zh_HKVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.版本檔案總管需要 2 個引數: +- 快照根目錄的路徑。 +- 要瀏覽的檔案名稱。nlUrgent: '%s' is almost full!Urgent: '% s' is bijna vol!frInvalid arguments count.Nombre d'arguments incorrect.jaSnapshot created successfullyスナップショットが正常に作成されましたarThe file system: '%s', exceeded %s%% of its total capacityنظام الملفات: '%s', تجاوز %s%% فوق كل السعة الكلية csDeleting SnapshotsMažu snímkyzh_TWEmergency: '%s' is full!緊急:「%s」已滿!fr%d snapshots will be deleted.%d instantanés seront supprimés.arFile Systems To Back Up ملف النظام للنسخة الإحتياطية idSnapshot NameNama SnapshotitConfigure the system to take automatic snapshots of your dataConfigurare il sistema per fare delle istantanee automatiche dei propri datihuExit when done. Kilépés, ha elkészült. zh_CNTime Slider File Version Explorer时间滑块文件版本资源管理器zh_TW1 snapshot will be deleted.將刪除 1 個快照。idFile Systems To Back UpFile Systems To Back Upde10 Mo10 ModeInsufficient PriviligesNicht ausreichend Zugriffsrechteko%d snapshots will be deleted.%d개의 스냅샷이 삭제됩니다.zh_CNMount Point挂载点jaFile System:ファイルシステム: idC_ustom:C_ustom:esC_ustom:_Personalizar:idUrgent: '%s' is almost full!Mendesak: '%s' hampir penuh!es_All_Todoeswith the name :con el nombre:zh_TW_Delete Snapshots...刪除快照(_D)...huErrorHibaidEmergency: '%s' is almost full!Darurat: '%s' hampir penuh!nlPress Delete to continue.Druk delete om door te gaan.pt_BREnable Time SliderAtivar a barra deslizante de tempofrOlder VersionsAnciennes versionsplThe file system: '%s', is over %s%% full.The file system: '%s', is over %s%% full.zh_TWSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 需要具有管理權限才能執行 [快照刪除] 操作。沒有為您指定所需的管理權限。 + +請諮詢系統管理員 csDeleting snapshots...Mažu snímky...zh_HKcompare對比itSnapshot can not be deletedImpossibile eliminare l’istantaneaarC_ustom:C_ustom:huSelect ZFS snapshots to delete from the list below.Jelöljön ki ZFS pillanatképeket törlésre a listából.huname :név :arScanning for older versionsالفحص للإصدارات الأقدم koInvalid characters in snapshot name스냅샷 이름에 잘못된 문자가 있습니다.zh_TWFile Systems To Back Up要備份的檔案系統zh_HKSummary摘要pt_BRAllTodosnlDeleting SnapshotsMomentopnames worden verwijdertcsEnable Time SliderPovolit Time SlideresThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.El servicio de administrador de instantáneas se ha quedado sin conexión por un problema de dependencia. Se han detectado los siguientes problemas de dependencia: + +%s + +Ejecute "svcs -xv" desde un indicador de comandos para obtener más información sobre estos problemas de dependencia.pt_BRWarning: '%s' is getting fullAviso: '%s' está ficando cheiozh_TWHourly每小時zh_CN_All全部(_A)itFile System:File system:zh_HK%d snapshots will be deleted.將刪除 %d 個快照。huError occured while creating the snapshotHiba lépett fel pillanatkép készítésekorjaMount PointマウントポイントarOlder Versionsالإصدارات السابقةitWeeklySettimanalezh_HKUrgent: '%s' is almost full!緊要:「%s」幾乎已滿!zh_HKExit when done. 完成後結束。 pt_BRRecommended for most usersRecomendado para a maioria dos usuáriosid10 Mo10 Mohuof file system capacityfájlrendszer kapacitásánakcaTake a snapshot of Fer una instantània de esD_eselect AllD_eseleccionar todoitHourlyOgni oranlMonthlyMaandelijkshuSome snapshots could not be deletedNéhány pillanatkép nem törölhetőfrSelect ZFS snapshots to delete from the list below.Sélectionnez les instantanés ZFS à supprimer dans la liste suivante.csfilenamejméno souborues10 Mo10 MoplFile Systems To Back UpSystem plików do Back UparScanning snapshots...فحص صور النظام pt_BRName:Nome:zh_CNExit when done. 完成后退出。 huRecommended for most usersAjánlott az átlagfelhasználóknaknlConfiguring Time Slider...Configuring Time Slider...nlFor advanced usersVoor geavanceerde gebruikersit_All_Tuttozh_HKDeleting Snapshots正在刪除快照pt_BRMonthlyMensalmenteidFor advanced usersUntuk pengguna lanjutandeErrorFehlerzh_CNCreation Time创建时间fr_All_Touszh_TWTime Slider Manager時間滑動軸管理員idSnapshot can not be deletedSnapshot tidak dapat dihapusjaEmergency: '%s' is full!緊急: '%s' がいっぱいです。itPress Delete to continue.Premere Elimina per continuare.zh_CNAdvanced Options高级选项zh_TW_All全部(_A)zh_CNfilename文件名cscompareporovnathuSelectKiválasztásplThe file system: '%s', exceeded %s%% of its total capacityThe file system: '%s', exceeded %s%% of its total capacityzh_TWTake a snapshot now立即擷取快照koFile System Name파일 시스템 이름huEmergency: '%s' is full!Vészhelyzet: '%s' megtelt!nlReduce backups when storage space usage exceeds:Verminder back-ups als opslagruimte gebruik meer bedraagt dan:jaC_ustom:カスタム(_U): pt_BRSummaryResumoitSelect ZFS snapshots to delete from the list below.Selezionare le istantanee ZFS da eliminare dall’elenco di seguito riportato.zh_CNTake a snapshot now立即捕获快照zh_CN10 Mo10 MoesTime Slider ManagerAdministrador del deslizador de tiempozh_CNname :名称:pt_BRTime SliderBarra deslizante de tempozh_HKFile System Name檔案系統名稱itThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).La capacità totale del file system '%s' è stata superata del %s%%. Come rimedio, Time Slider ha eliminato alcuni backup e ne eliminerà altri o tutti man mano che la capacità diminuisce. +Per evitare il ripetersi del problema, eliminare eventuali file non necessari in '%s' oppure aggiungere spazio sul disco (vedere la documentazione di ZFS).zh_HKError錯誤arOpen the current version of the fileإفتح نفس إصدار الملف cs1/4 HourlyKaždou 1/4 hodinufrAdvanced OptionsOptions avancéesitsize : dimensioni : zh_HKDeleting snapshots...正在刪除快照...pt_BRScanning for older versionsExplorando versões mais antigasfrTake a snapshot nowPrenez un instantanéplFile System:System plików:idWarning: '%s' is getting fullPeringatan: '%s' mulai penuhfrfilenamenom de fichierfrMonthlyMensuelleplWarning: '%s' is getting fullOstrzeżenie: '%s' staje się pełnyesSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Tomar instantánea ahora necesita privilegios administrativos. No se le han asignado los correspondientes privilegios administrativos. + +Consulte al administrador del sistemafrEnable Time SliderActiver le curseur temporeljaTime Slider ManagerタイムスライダマネージャーcsFile Systems To Back UpZáloha souborového systémufrFile System:Système de fichiers :caFor advanced usersPer a usuaris avançatsca1 snapshot will be deleted.1 instantània serà eliminada.nlInsufficient PriviligesOnvoldoende PrivilegesarTime Sliderشريط تمرير الوقت deSuccessErfolgfrTime SliderCurseur temporelzh_CNWeekly每周frC_ustom:Pers_onnalisé :frSnapshot NameNom de l'instantanécaDouble click to open the fileDoble clic per a obrir un fitxerzh_CNDouble click to open the file双击以打开文件arEmergency: '%s' is almost full!الخظر : '%s' على الأغلب كامل jaTake a snapshot of スナップショットを取得する対象 caTake a snapshot nowFer una instantània aradeMount PointEinhängepunktcsEmergency: '%s' is full!Stav nouze: '%s' je plný!pt_BRTake a snapshot of Efetuar um instantâneo de huDelete SnapshotsPillanatképek törlésefrUrgent: '%s' is almost full!Urgent : %s est presque plein !it10 Mo10 MoesWarning: '%s' is getting fullAdvertencia: '%s' se está llenandocaTime Slider File Version ExplorerExplorador de Versions de Fitxer de Time SliderkoThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.스냅샷 관리자 서비스에 문제가 발생하여 문제가 수정될 때까지 비활성화되었습니다. + +자세한 내용은 svcs(1) 매뉴얼 페이지를 참조하십시오.plTime Slider: Low Space WarningTime Slider: Low Space WarningkoScanning for older versions이전 버전 검색 중itThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Per l'esecuzione del servizio di gestione delle istantanee sono necessari privilegi amministrativi. Non si dispone di privilegi amministrativi sufficienti. + +Rivolgersi all'amministratore di sistema. pl1/4 HourlyCo 15 minutcs_Delete Snapshots..._Smazat snímek...jaSummary要約arSuccessنجاح zh_CNFor advanced users适用于高级用户jaSelect ZFS snapshots to delete from the list below.下の一覧から削除する ZFS スナップショットを選択します。koConfigure the system to take automatic snapshots of your data사용자 데이터의 자동 스냅샷을 만들도록 시스템 구성esThe snapshot manager service requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator El servicio de administrador de instantáneas necesita privilegios administrativos. No se le ha asignado los correspondientes privilegios administrativos. + +Consulte al administrador del sistema fr%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s a un ou plusieurs clones dépendants et ne sera pas supprimé. Supprimez au préalable tous les jeux de données et instantanés clonés à partir de cet instantané pour pouvoir supprimer ce dernier.nlEnable Time SliderSchakel tijd slijder inzh_TWDelete Snapshots刪除快照koCurrent Version현재 버전zh_TWFile System Name檔案系統名稱idof file system capacitydari kapasitas file sistemjaPress Delete to continue.続行するには「削除」を押します。itFile Systems To Back UpFile system di cui eseguire il backupfrDeleting SnapshotsSuppression des instantanésdeTake a snapshot nowJetzt einen Schnappschuss nehmenarAllالكل zh_TWRecommended for most users建議大多數使用者採用pt_BRThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).O sistema de arquivos: '%s' excedeu %s%% da capacidade total. Como medida de reforço, a barra deslizante de tempo eliminou alguns backups e eliminará mais, eventualmente todos, caso a capacidade continue diminuindo. +Para evitar que isso acorra novamente, exclua os arquivos desnecessários de '%s' ou adicione espaço em disco (consulte a documentação do ZFS).zh_HK_All全部(_A)nlSnapshot manager service errorMomentopname manager service foutdeFor advanced usersFür erfahrene Benutzerid1 snapshot will be deleted.1 snapshot akan di hapusplEmergency: '%s' is full!Uwaga: '%s' jest zapełnionyzh_TWAll全部es1 snapshot will be deleted.Se eliminará 1 instantánea.caDeleting SnapshotsEliminant Instantànieszh_HKLegacy原來的zh_CNSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator 需要有管理权限才能删除快照。没有为您指定必要的管理权限。 + +请咨询您的系统管理员 pt_BRDeleting snapshots...Excluindo instantâneos...jaVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.バージョンエクスプローラには次の 2 つの引数が必要です: +- ルートスナップショットディレクトリのパス。 +- 調査するファイル名。nl1 snapshot will be deleted.1 momentopname zal niet verwijdert wordenhuMonthlyHaviplScanning for older versionsWyszukuje starsze wersjejaFile System Nameファイルシステム名itRecommended for most usersPer tutti gli utentinlScanning snapshots...Momentopnamen aan het scanderen...idLegacyLegacynlSelectSelecteeritSnapshot manager service dependency errorErrore di dipendenza del servizio di gestione delle istantaneekoThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.스냅샷 관리자 서비스가 이 시스템에 설치된 것으로 나타나지 않습니다. + +자세한 내용은 svcs(1) 매뉴얼 페이지를 참조하십시오.zh_CNSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.立即进行快照需要 2 个参数: +- 要进行快照的目录的路径。 +- 与此目录相对应的 zfs 文件系统。pt_BRLegacyLegadoesWeeklyCada semanaplHintHintfrcomparecomparaisonidTime Slider backs up data regularly by taking timed ZFS SnapshotsTime Slider membackup data secara teratur berdasarkan waktu Snapshot ZFSit%d snapshots will be deleted.Verranno eliminate %d istantanee.esgtk-closegtk-cerraresThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.El servicio de administrador de instantáneas parece no estar instalado en este sistema. + +Para obtener más información, consulte la página de comando man svcs(1).caHintPistapt_BRFile System:Sistema de arquivos:huScanning for older versionsKeresés régebbi verziók utánjaConfigure the system to take automatic snapshots of your data自動的にデータのスナップショットを作成するようにシステムを構成しますhuEmergency: '%s' is almost full!Vészhelyzet: '%s' majdnem teli!csSnapshot can not be deletedSnímek nemůže být smazánhu1 snapshot will be deleted.1 pillanatkép kerül törlésre.pt_BRTake a snapshot nowEfetuar um instantâneo agorapt_BR_Delete Snapshots..._Excluir instantâneos...nlCreation TimeAanmaaktijddeThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.Der Schnappschussverwaltungsdienst wurde wegen eines Abhängigkeitsproblems offline geschaltet. Folgende Abhängigkeitsprobleme wurden festgestellt: + +%s + +Führen Sie über eine Eingabeaufforderung den Befehl "svcs -xv" aus, um weitere Informationen zu diesen Abhängigkeitsproblemen zu erhalten.nlScanning for older versionsZoeken naar oudere versiescsgtk-closegtk-closejaRecommended for most usersほとんどのユーザーに推奨pl19/01/0919/01/09zh_TWThe snapshot manager service has been placed offline due to a dependency problem. The following dependency problems were found: + +%s + +Run "svcs -xv" from a command prompt for more information about these dependency problems.由於相依性問題,已將快照管理員服務置為離線。發現以下相依性問題: + +%s + +請從指令提示符號下執行「svcs –xv」,以取得有關這些相依性問題的更多資訊。itSnapshot created successfullyIstantanea creata con successozh_CNFile System Name文件系统名称pt_BRname :nome :csTake a snapshot of Pořídit snímek idSnapshot manager service dependency errorKesalahan dependensi layanan pengendali SnapshotarInvalid characters in snapshot nameخطأ برموز إسم صورة النظام frSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Pour utiliser l'option Prendre un instantané, vous devez posséder des privilèges administratifs. Vous ne possédez pas les privilèges administratifs requis. + +Consultez votre administrateur système huEnable Time SliderTime Slider engedélyezésezh_TWgtk-closegtk-closedeSnapshot deletion requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Die Löschung von Schnappschüssen erfordert Administratorrechte. Ihnen wurden nicht die nötigen Administratorrechte zugewiesen. + +Wenden Sie sich an Ihren Systemadministrator. jaThe file system: '%s', exceeded %s%% of its total capacity. As a remedial measure, Time Slider has destroyed some backups, and will destroy more, eventually all, as capacity continues to diminish. +To prevent this from happening again, delete any unnecessary files on '%s', or add disk space (see ZFS documentation).ファイルシステム: '%s' が総容量の %s%% を超えました。是正手段として、タイムスライダはすでに一部のバックアップを破棄しており、容量が減り続ければさらに多くのバックアップ、最終的にはすべてのバックアップを破棄します。 +この問題がふたたび発生しないようにするために、'%s' 上の不必要なファイルをすべて削除するか、またはディスク容量を追加してください (ZFS のマニュアルを参照)。plof file system capacitymożliwości systemu plikówkoSnapshot manager service dependency error스냅샷 관리자 서비스 종속성 오류frThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.Le service de gestionnaire d'instantanés a rencontré un problème et a été désactivé jusqu'à la résolution du problème. + +Pour de plus amples informations, reportez-vous à la page de manuel svcs(1).zh_HKgtk-closegtk-closekoSnapshot can not be deleted스냅샷을 삭제할 수 없습니다.csC_ustom:V_lastníja1/4 Hourly15 分ごとkoAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +스냅샷 이름에 사용할 수 있는 문자는 다음과 같습니다. +[a-z][A-Z][0-9][-_.: +잘못된 문자는 모두 제거됩니다. +pt_BREmergency: '%s' is full!Emergência: '%s' está cheio!frDelete SnapshotsSupprimer des instantanéskoOlder Versions이전 버전zh_TWPress Delete to continue.請按下 [刪除] 繼續。koScanning snapshots...스냅샷 검색 중...huSnapshot manager service errorPillanatkép kezelő szolgáltatás hibacaD_eselect AllDe-_seleccionar-ho TotarMonthlyشهريا nlLegacyLegaatkoFile System:파일 시스템:koError오류ca%d snapshots will be deleted.%d instantànies a ser eliminades.huConfiguring Time Slider...Time Slider konfigurációja...arCreation Timeإنشاء الوقت huInvalid arguments count.Érvénytelen argumentum szám.zh_TWsize : 大小: itSuccessOperazione riuscitafrSome snapshots could not be readCertains instantanés n'ont pas pu être luscsFile System NameJméno souborového systémuitThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.Il servizio di gestione delle istantanee ha riscontrato un problema ed è stato disabilitato fino alla risoluzione del problema. + +Per ulteriori informazioni consultare la pagina man svcs(1).deTime Slider ManagerZeit-Schieberegler-Managerpt_BRSnapshot created successfullyInstantâneo criado com êxitozh_CN_Delete Snapshots...删除快照(_D)...koTake a snapshot of 다음 항목의 스냅샷 실행 ko1 snapshot will be deleted.1개의 스냅샷이 삭제됩니다.itMount PointPunto di attivazionept_BRVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.O explorador de versão requer 2 argumentos : +- O caminho do diretório raiz do instantâneo. +- O nome de arquivo para exploração.frSuccessTerminé sans erreuridFile System NameNama File SistemitWarning: '%s' is getting fullAvvertenza: lo spazio su '%s' sta per esaurirsiarfilenameإسم الملف frFor advanced usersPour les utilisateurs expérimentészh_HKHourly每小時nlof file system capacityvan bestand systeem capaciteitpt_BRErrorErronlC_ustom:C_ustom:zh_HKMount Point掛載點deTime Slider backs up data regularly by taking timed ZFS SnapshotsDer Zeit-Schieberegler sichert regelmäßig Daten durch Erfassung von zeitlich festgelegten ZFS-SchnappschüssencsSome snapshots could not be readNěkteré snímky nelze přečístzh_TWD_eselect All全部取消選取(_E)itA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +Un’istantanea del file system zfs %(zfs_fs)s +denominata %(valid_name)s +è stata creata. +zh_CNConfigure the system to take automatic snapshots of your data将系统配置为自动捕获数据的快照zh_TWSnapshot manager service error快照管理員服務錯誤zh_TWInvalid characters in snapshot name快照名稱中有無效字元plScanning snapshots...Wyszukuje migawki...nlSelect ZFS snapshots to delete from the list below.Kies de te verwijderen ZFS momentopnamen van de lijst beneden. arTime Slider: Low Space Warningشريط التوقيت : تحذير بإنخفاض المساحةesTime Slider File Version ExplorerExplorador de versiones de archivos del deslizador de tiempoesOpen the current version of the fileAbrir la versión actual del archivojaErrorエラーzh_HK10 Mo10 MofrDeleting snapshots...Suppression des instantanés...nlMount PointMonteer puntjaScanning for older versions旧バージョンをスキャンしていますpt_BR%d snapshots will be deleted.%d instantâneos serão excluídos.zh_TWThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.快照管理員服務似乎並未安裝於此系統上。 + +請參閱「svcs(1) 線上手冊」,以取得更多資訊。idName:Nama:jaEnable Time Sliderタイムスライダを有効にするidSome snapshots could not be deletedBeberapa snapshot tidak dapat dihapuszh_HKScanning snapshots...正在掃描快照...plTime Slider ManagerMenadżer Time Sliderar10 Mo10 MoitName:Nome:zh_HKInvalid arguments count.引數計數無效。koLegacy레거시nlEmergency: '%s' is full!Alarm: '% s' is vol!csMonthlyMěsíčněja%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s には 1 つ以上の複製が依存しているため、削除されません。このスナップショットを削除するには、まず、このスナップショットから複製されたすべてのデータセットとスナップショットを削除してください。zh_CNHourly每小时frD_eselect AllTout désél_ectionnercaThe file system: '%s', exceeded %s%% of its total capacityEl sistema de fitxers: '%s', excedeix %s%% de la seva capacitat totaljaName:名前: caSuccessRealitzatid1/4 HourlyTiap 1/4 jamjaCreation Time作成時間plDelete SnapshotsUsuwam migawkiarWeeklyإسبوعيا zh_HKThe snapshot manager service has encountered a problem and has been disabled until the problem is fixed. + +See the svcs(1) man page for more information.快照管理員服務遇到了問題,而且在問題修正之前都處於停用狀態。 + +請參閱「svcs(1) 線上手冊」,以取得更多資訊。esAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +Los caracteres permitidos en los nombres de las instantáneas son: +[a-z][A-Z][0-9][-_.: +Se suprimirán todos los caracteres no válidos +pt_BRHourlyDe hora em horakoSnapshot created successfully스냅샷이 성공적으로 생성되었습니다.zh_TWAllowed characters for snapshot names are : +[a-z][A-Z][0-9][-_.: +All invalid characters will be removed +允許用於快照名稱的字元如下: +[a-z]、[A-Z]、[0-9]、[、-、_、.、: +將移除所有無效字元 +plD_eselect AllO_dznacz wszystkiecaScanning for older versionsEscanejant per a versions antigueszh_CNRecommended for most users建议大多数用户采用plName:Nazwa:pt_BRD_eselect AllCancelar s_eleção de todoshu_All_Mindarcompareمقارنة idConfigure the system to take automatic snapshots of your data zh_HKname :名稱:huTake a snapshot nowPillanatkép készítése mostes%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s tiene uno o más duplicados y no se puede eliminar. Para eliminar esta instantánea, primero debe borrar todos sus conjuntos de datos e instantáneas duplicados.itwith the name :con il nome :zh_CNFile System:文件系统:idSelect ZFS snapshots to delete from the list below.Pilih ZFS snapshots untuk menghapus dari daftar dibawah ini.frSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.L'option Prendre un instantané nécessite 2 arguments : +- Chemin d'accès au répertoire dont un instantané doit être pris. +- Système de fichiers ZFS correspondant à ce répertoire.idEnable Time SliderEnable Time Sliderzh_CNScanning for older versions正在扫描早期版本esEmergency: '%s' is full!Emergencia: '%s' se ha llenadokoSome snapshots could not be deleted일부 스냅샷을 삭제할 수 없습니다.itEmergency: '%s' is full!Attenzione: '%s' è pieno.zh_HKFile System:檔案系統:jaA snapshot of zfs filesystem %(zfs_fs)s +named %(valid_name)s +has been created. +zfs ファイルシステム %(zfs_fs)s のスナップショット +(名前は %(valid_name)s) +が作成されました。 +jawith the name :名前: nlDailyDagelijksko10 Mo10MoesCurrent VersionVersión actualitSelectSelezionanlcomparevergelijkcsTime Slider backs up data regularly by taking timed ZFS SnapshotsTime Slider zálohuje pravidelně data pomocí časových ZFS smínkůesFile Systems To Back UpSistemas de archivos de los que hacer copia de seguridadzh_HKSome snapshots could not be deleted無法刪除某些快照idError occured while creating the snapshotTerjadi error ketika membuat snapshotzh_HKEmergency: '%s' is full!緊急:「%s」已滿!caOpen the current version of the fileObrir la versió actual del fitxerplcompareporównajzh_TWCreation Time建立時間idTake a snapshot of Mengambil snapshot dari zh_TWVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.版本檔案總管需要 2 個引數: +- 快照根目錄的路徑。 +- 要瀏覽的檔案名稱。jasize : サイズ: ko%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s에 하나 이상의 종속 복제가 있어 삭제되지 않습니다. 이 스냅샷을 삭제하려면 먼저 이 스냅샷에서 복제된 모든 데이터 집합과 스냅샷을 삭제하십시오.pt_BRcomparecompararcsof file system capacitykapacity souborového systémucaCreation TimeTemps de creacióplError occured while creating the snapshotWystąpił błąd podczas tworzenia migawkizh_CNSome snapshots could not be deleted无法删除某些快照huReduce backups when storage space usage exceeds:Csökkentse a mentések számát ha a tároló helyhasználata caInvalid characters in snapshot nameCaràcters no vàlids al nom de la instantàniafrExit when done. Quitter une fois l'opération terminée. nlAdvanced OptionsGeavanceerde optiesarTime Slider Managerإدارة شريط تمرير الوقت frMount PointPoint de montagefrError occured while creating the snapshotUne erreur s'est produite lors de la création de l'instantanéfrSnapshot manager service errorErreur du service de gestionnaire d'instantanéscsConfiguring Time Slider...Konfiguruji Time Slider...koD_eselect All모두 선택 해제(_E)csUrgent: '%s' is almost full!Naléhavé: '%s' je skoro plný!deEmergency: '%s' is full!Notfall: '%s' ist voll!jaExit when done. 完了したら終了します。 es%d snapshots will be deleted.Se eliminarán %d instantáneas.jaError occured while creating the snapshotスナップショットの作成中にエラーが発生しましたpt_BR%s has one or more dependent clones and will not be deleted. To delete this snapshot, first delete all datasets and snapshots cloned from this snapshot.%s tem um ou mais clones dependentes e não será excluído. Para excluir este instantâneo, exclua primeiro todos os conjuntos de dados e instantâneos clonados deste instantâneo.zh_CNMonthly每月idEmergency: '%s' is full!Darurat: '%s' penuh!nl1/4 HourlyIeder kwartieridScanning snapshots...Memindai snapshots...deVersion explorer requires 2 arguments : +- The path of the root snapshot directory. +- The filename to explore.Versions-Explorer erfordert 2 Argumente: +- Pfad des Schnappschuss-Stammverzeichnisses. +- Dateiname, nach dem gesucht werden soll.zh_CNAll全部zh_HKSelect選取huSummaryÖsszesítéscagtk-closetancar-gtkesAllTodoscsOpen the current version of the fileOtevřete soubor současné verzecacomparecompararzh_TWConfigure the system to take automatic snapshots of your data將系統配置為自動對資料執行快照frCreation TimePériode de créationzh_HKSnapshot can not be deleted無法刪除快照koOpen the current version of the file파일의 현재 버전을 엽니다.esTime SliderDeslizador de tiempozh_HKSnapshot manager service dependency error快照管理員服務相依性錯誤zh_CNThe snapshot manager service does not appear to be installed on this system. + +See the svcs(1) man page for more information.该快照管理器服务似乎没有安装在此系统上。 + +有关更多信息,请参见 svcs(1) 手册页。nlAllAllecaTime SliderTime Slideridsize : ukuran : esMount PointPunto de montajept_BRScanning snapshots...Explorando instantâneos...huSnapshot can not be deletedNem törölhető pillanatképjaHourly毎時caSnapshot manager service dependency errorError de dependència del servei gestor de instantànieszh_HKName:名稱:frSnapshot created successfullyL'instantané a été créécaSome snapshots could not be deletedNo s'han pogut eliminar algunes instantàniesplEnable Time SliderWłącz Time Slideresof file system capacityde capacidad de sistema de archivosjaMonthly毎月cs1 snapshot will be deleted.1 snímek bude smazánzh_CN%d snapshots will be deleted.%d 个快照将被删除。plFor advanced usersDla zaawansowanych użytkownikówzh_TWError occured while creating the snapshot建立快照時發生錯誤zh_CNReduce backups when storage space usage exceeds:当存储空间使用率超过以下值时减少备份:ar19/01/0919/01/09deInvalid arguments count.Ungültige Argumente.csFile System:Souborový systém:itSnapshot Now requires administrative privileges to run. You have not been assigned the necessaryadministrative priviliges. + +Consult your system administrator Per l’esecuzione del comando Crea istantanea sono necessari privilegi amministrativi. Non si dispone dei privilegi amministrativi necessari. + +Rivolgersi all'amministratore di sistema. nlThe file system: '%s', exceeded %s%% of its total capacityThe file system: '%s', exceeded %s%% of its total capacityhu_Delete Snapshots..._Pillanatkép törlése...pt_BRDeleting SnapshotsExcluindo instantâneoskoInvalid arguments count.잘못된 인수 카운트입니다.zh_TWSuccess成功nlExit when done. Sluit af als je klaar bent. csCreation TimeČas vytvořenízh_HKfilename檔案名稱zh_HKTime Slider backs up data regularly by taking timed ZFS Snapshots時間調整器會透過擷取定時的 ZFS 快照,定期備份資料nlWarning: '%s' is getting fullWaarschuwing: '%s' wordt voller en vollerzh_TWExit when done. 完成後結束。 esExit when done. Salir al terminar. zh_CNWarning: '%s' is getting full警告:'%s' 即将被占满nlTime Slider backs up data regularly by taking timed ZFS SnapshotsTijd glijder maakt reservekopieën van gegevens door het nemen van een regelmatige ZFS momentopnamenjaWarning: '%s' is getting full警告: '%s' がいっぱいになりつつありますkoError occured while creating the snapshot스냅샷 생성 중 오류가 발생했습니다.zh_TWname :名稱:zh_HKDaily每天huHourlyÓránkéntiid19/01/0919/01/09zh_HKSnapshot Name快照名稱idHourlyTiap JamarFile System:نظام الملفات : pt_BRSelect ZFS snapshots to delete from the list below.Selecione na lista abaixo os instantâneos do ZFS que serão excluídos.arWarning: '%s' is getting fullتحذير : '%s' الحصول على الكامل jaFile Systems To Back UpバックアップするファイルシステムcaDetails...Detalls...it1 snapshot will be deleted.1 istantanea verrà eliminata.itgtk-closegtk-closeit19/01/0919/01/09zh_CNTime Slider时间滑块frTime Slider backs up data regularly by taking timed ZFS SnapshotsLe curseur temporel sauvegarde les données régulièrement en prenant des instantanés ZFS différésescomparecompararfrErrorErreurnlsize : grootte : zh_CNEnable Time Slider启用时间滑块esSnapshot manager service dependency errorError de dependencia del servicio de administrador de instantáneaspt_BRTime Slider ManagerGerenciador da barra deslizante de temponlFile System NameBestand systeem naamdeInvalid characters in snapshot nameSchnappschussname enthält ungültige Zeichenzh_CNFile Systems To Back Up要备份的文件系统caSome snapshots could not be readNo s'han pogut llegir algunes instantàniesplConfiguring Time Slider...Configuring Time Slider...arMount Pointنقطة التحميل deSnapshot manager service dependency errorAbhängigkeitsfehler im Zusammenhang mit dem SchnappschussverwaltungsdienstcaExit when done. Sortir quant estigui fet. idHintPetunjukdeConfigure the system to take automatic snapshots of your dataSystem für automatische Snapshots Ihrer Daten konfigurierenzh_HKInvalid characters in snapshot name快照名稱中有無效字元koWeekly매주esReduce backups when storage space usage exceeds:Destruir las copias de seguridad cuando se sobrepase el uso del espacio de almacenamiento:koSnapshot Name스냅샷 이름esFile System:Sistema de archivos:arEmergency: '%s' is full!الخطر : '%s' ممتلئ!ja1 snapshot will be deleted.1 つのスナップショットが削除されます。nlSnapshot NameMomentopname naamesDelete SnapshotsEliminar instantáneasnlWeeklyWekelijkszh_CNTime Slider Manager时间滑块管理器zh_TWMount Point掛載點eslast modified : última modificación: zh_CNOlder Versions早期版本pt_BRSuccessÊxitohuThe file system: '%s', is over %s%% full.A fájlrendszer: '%s' több mint %s%%-kal megtelve.csThe file system: '%s', exceeded %s%% of its total capacitySouborový systém: '%s', překročil %s%% své celkové kapacitydeD_eselect AllA_uswahl aufhebenitEnable Time SliderAbilita Time Slider cswith the name :s názvem:frWeeklyHebdomadaireplTime SliderTime SliderkoMount Point마운트 지점deOlder VersionsÄltere Versionenpt_BRwith the name :com o nome:zh_TWTime Slider backs up data regularly by taking timed ZFS Snapshots時間調整器會透過擷取定時的 ZFS 快照,定期備份資料plEmergency: '%s' is almost full!Uwaga: '%s' jest prawie zapełnionynlFile System:Bestand systeem:esSnapshot Now requires 2 arguments : +- The path of the directory to be snapshotted. +- The zfs filesystem corresponding to this directory.Tomar instantánea ahora necesita 2 argumentos: +- La ruta del directorio del que se va a tomar la instantánea. +- El sistema de archivos zfs correspondiente a este directorio.frEmergency: '%s' is full!Urgence : %s est plein !esName:Nombre:zh_TWScanning snapshots...正在掃描快照...idRecommended for most usersDirekomendasikan untuk sebagian besar penggunaarThe file system: '%s', is over %s%% full.نظام الملفات: '%s', كامل %s%% ممتلئ.zh_CNDelete Snapshots删除快照deEnable Time SliderZeit-Schieberegler aktivierenidThe file system: '%s', exceeded %s%% of its total capacityFile Sistem: '%s', melebihi %s%% dari kapasitas total.janame :名前: huPress Delete to continue.Nyomjon Delete-t a folytatáshoz. \ No newline at end of file diff --git a/po/ChangeLog b/po/ChangeLog new file mode 100644 index 0000000..d61d752 --- /dev/null +++ b/po/ChangeLog @@ -0,0 +1,27 @@ +2010-01-21 Harry Fu + + * LINGUAS: new languages are added - ar,id,nl + * *.po: Updated cs/hu/pl with the latest translations. + +2009-02-19 Takao Fujiwara + + * POTFILES.in: Added time-slider-version.glade and fileversion.py + * *.po: Updated with the latest strings. + +2008-11-04 Takao Fujiwara + + * *.po: Updated with the latest strings. + +2008-10-20 Takao Fujiwara + + * LINGUAS: Added to compile .mo files. + * Makefile: Added. + * POTFILES.in: Added for intltool. + * cs.po de.po es.po fr.po hu.po it.po ja.po ko.po pl.po pt_BR.po ru.po + sv.po zh_CN.po zh_HK.po zh_TW.po: Added .po files. + diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..fd836de --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1,21 @@ +# please keep this list sorted alphabetically +# +ar +ca +cs +de +es +fr +hu +id +it +ja +ko +nl +pl +pt_BR +ru +sv +zh_CN +zh_HK +zh_TW diff --git a/po/Makefile b/po/Makefile new file mode 100644 index 0000000..5e315d0 --- /dev/null +++ b/po/Makefile @@ -0,0 +1,93 @@ +srcdir = . +top_srcdir = .. +top_builddir = .. + +subdir = po +prefix = /usr +DATADIRNAME = share +itlocaledir = $(prefix)/$(DATADIRNAME)/locale +LOCALE_OWNER = root +LOCALE_GROUP = root +#mkdir_p = install -d -m 755 -o $(LOCALE_OWNER) -g $(LOCALE_GROUP) +mkdir_p = install -d -m 755 + +PACKAGE = $(firstword $(patsubst PACKAGE=%, %, $(shell grep "^PACKAGE=" $(top_srcdir)/Makefile))) +VERSION = $(firstword $(patsubst VERSION=%, %, $(shell grep "^VERSION=" $(top_srcdir)/Makefile))) +GETTEXT_PACKAGE = $(PACKAGE) + +INSTALL = install -c +INSTALL_DATA = ${INSTALL} -m 644 + +GMSGFMT = msgfmt +MSGFMT = msgfmt + +ALL_LINGUAS = + +PO_LINGUAS=$(shell if test -r $(srcdir)/LINGUAS; then grep -v "^\#" $(srcdir)/LINGUAS; fi) + +USER_LINGUAS=$(shell if test -n "$(LINGUAS)"; then LLINGUAS="$(LINGUAS)"; ALINGUAS="$(ALL_LINGUAS)"; for lang in $$LLINGUAS; do if test -n "`grep ^$$lang$$ $(srcdir)/LINGUAS`" -o -n "`echo $$ALINGUAS|grep ' ?$$lang ?'`"; then printf "$$lang "; fi; done; fi) + +USE_LINGUAS=$(shell if test -n "$(USER_LINGUAS)"; then LLINGUAS="$(USER_LINGUAS)"; else if test -n "$(PO_LINGUAS)"; then LLINGUAS="$(PO_LINGUAS)"; else LLINGUAS="$(ALL_LINGUAS)"; fi; fi; for lang in $$LLINGUAS; do printf "$$lang "; done) + +POFILES=$(shell LINGUAS="$(USE_LINGUAS)"; for lang in $$LINGUAS; do printf "$$lang.po "; done) + +DISTFILES = Makefile POTFILES.in $(POFILES) +EXTRA_DISTFILES = LINGUAS + +CATALOGS=$(shell LINGUAS="$(USE_LINGUAS)"; for lang in $$LINGUAS; do printf "$$lang.gmo "; done) + +.SUFFIXES: +.SUFFIXES: .po .gmo + +.po.gmo: + file=`echo $* | sed 's,.*/,,'`.gmo \ + && rm -f $$file && $(GMSGFMT) -o $$file $< + +all: all-yes + +all-yes: $(CATALOGS) +all-no: + +install: install-data +install-data: install-data-yes +install-data-no: all +install-data-yes: all + $(mkdir_p) $(DESTDIR)$(itlocaledir) + linguas="$(USE_LINGUAS)"; \ + for lang in $$linguas; do \ + dir=$(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES; \ + $(mkdir_p) $$dir; \ + if test -r $$lang.gmo; then \ + $(INSTALL_DATA) $$lang.gmo $$dir/$(GETTEXT_PACKAGE).mo; \ + echo "installing $$lang.gmo as $$dir/$(GETTEXT_PACKAGE).mo"; \ + else \ + $(INSTALL_DATA) $(srcdir)/$$lang.gmo $$dir/$(GETTEXT_PACKAGE).mo; \ + echo "installing $(srcdir)/$$lang.gmo as" \ + "$$dir/$(GETTEXT_PACKAGE).mo"; \ + fi; \ + done + +uninstall: + linguas="$(USE_LINGUAS)"; \ + for lang in $$linguas; do \ + rm -f $(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE).mo; \ + done + +clean: + rm -f *.gmo + rm -f .intltool-merge-cache + +distdir = ../$(PACKAGE)-$(VERSION)/$(subdir) +dist distdir: $(DISTFILES) + dists="$(DISTFILES)"; \ + extra_dists="$(EXTRA_DISTFILES)"; \ + for file in $$extra_dists; do \ + test -f $(srcdir)/$$file && dists="$$dists $(srcdir)/$$file"; \ + done; \ + for file in $$dists; do \ + test -f $$file || file="$(srcdir)/$$file"; \ + ln $$file $(distdir) 2> /dev/null \ + || cp -p $$file $(distdir); \ + done + +.NOEXPORT: diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..8252e1e --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,12 @@ +# List of source files containing translatable strings. +# Please keep this file sorted alphabetically. +data/time-slider.desktop.in +usr/share/time-slider/glade/time-slider-delete.glade +usr/share/time-slider/glade/time-slider-setup.glade +usr/share/time-slider/glade/time-slider-snapshot.glade +usr/share/time-slider/glade/time-slider-version.glade +usr/share/time-slider/lib/time_slider/applet.py +usr/share/time-slider/lib/time_slider/deletegui.py +usr/share/time-slider/lib/time_slider/fileversion.py +usr/share/time-slider/lib/time_slider/setupgui.py +usr/share/time-slider/lib/time_slider/snapnowui.py diff --git a/po/ar.gmo b/po/ar.gmo new file mode 100644 index 0000000000000000000000000000000000000000..819fdc3dc272070973bacbd710eed5f956515ab3 GIT binary patch literal 13263 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 literal 0 HcmV?d00001 diff --git a/po/ar.po b/po/ar.po new file mode 100644 index 0000000..f3b6635 --- /dev/null +++ b/po/ar.po @@ -0,0 +1,533 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" + "Project-Id-Version: time-slider HEAD\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2009-12-07 22:37+0900\n" + "PO-Revision-Date: 2010-01-20 20:31+0100\n" + "Last-Translator: Hosam Al Ali\n" + "Language-Team: Arabic language team \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "تاكيد النظام على أخذ صورة عن النظام عن البيانات " + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "شريط تمرير الوقت " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d الصورة سوف تمحى " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr " ملخص " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "D_eselect All" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "حذف صور النظام " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "حذف صور النظام " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "حذف صور النظام..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "الخروج عن الإنتهاء " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "نظام الملفات : " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "إسم : " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "إضغط على حذف للإستمرار " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "فحص صور النظام " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "إختر ZFS صور النظام لحذف من اسفل القائمة " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "تفعيل شريط تمرير الوقت" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr " ملف النظام للنسخة الإحتياطية " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "خيارات إضافية " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "C_ustom:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "إعدادات شريط التوقيت..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "من أجل المستخدمين المتقدمين " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "المطلوب من كل المستخدمين " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "تخفيض النسخة الإحتياطية عند تجاوز المساحة النخزينية " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "إدارة شريط تمرير الوقت " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "شريط تمرير الوقت يقوم بنسخ إحتياطي للبيانات بشكل منتظم " + "بأخذ صورة للنظام " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_الكل " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_حذف صور النظام ...." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "قدرة نظام ملفات النظام " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "خذ نسخة من صورة النظام الأن " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "خذ نسخة من صورة النظام " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "بإسم :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "الإصدار الحالي" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "الإصدارات السابقة" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "إضغط مرتين لفتح الملف " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "إفتح نفس إصدار الملف " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "الفحص للإصدارات الأقدم " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "متصفح ملف إصدار شريط تمرير الوقت " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "مقارنة " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "إسم الملف " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-إغلاق " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "أخر تعديل : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "إسم : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "الحجم : " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "شريط التوقيت : تحذير بإنخفاض المساحة" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "الخطر : '%s' ممتلئ!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "نظام الملفات: '%s', كامل %s%% ممتلئ." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + "نظام الملفات : `%s` وكامل %s%%. ممتلى \n" + "والتدبير الإحترازي, شريط تمرير الوقت سوف يحذف كامل النسخ الإحتياطي . \n" + "ولإصلاح هذه المشكلة , إحذف أي ملفات غير ضرورية '%s', أو أضف قرص أخر " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "الخظر : '%s' على الأغلب كامل " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "نظام الملفات: '%s', تجاوز %s%% فوق كل السعة الكلية " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + "نظام الملفات: '%s', تجاوز %s%% من اجمالي قدرتها. باعتبارها emerency" + "قياس ، شريط الوقت سوف يلغي معظم ا كل ملفات النسخة الإحتياطي لمنع ملئ القرص . لمنع حدوث ذلك مرة أخرى, يجب عليك , delete any, حذف كل الملفات الغير مفيدة ضمنه '%s', أو أضف قرص جديد(إنظر ZFS دليل)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "عاجل : '%s' على الإغلب ممتلئ " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "تحذير : '%s' الحصول على الكامل " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + "'%s' تجاوزت %s%% كامل المساحة المتاحة. لحل المشكلة, شريط الوقت يحذف كل ويزيل أخر ملفات النسخ الإحتياطي, وسوف يستمر بعملية الحذف الإزالة من أجل التقليص .\n" + "لمنع حدوث ذلك مرة أخرى , يجب عليك حذف الملفات الغير مفيدة ضمنه'%s', " + "أو اضف قرص جديد (إنظر ZFS دليل).\n" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "تفاصيل..." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "إرث " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "نقطة التحميل " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "إسم ملف النظام " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "إسم صورة النظام " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "إنشاء الوقت " + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "الكل " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "شهريا " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "إسبوعيا " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "يومي " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "ساعي " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "1/4 ساعي " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "صور النظام لا يمكن حذفها " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + "%s هناك وأحد او أكثر من نسخ معتمدة على بعضها لا يمكن خذفها . " + "لحذف صورة النظام , أولا أحذف كل datasetes ونسخ صور النظام من صور النظام " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 صورة نظام سوف تحذف " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "بعض صور النظام لايمكن أن تقرأ " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "بعض صور النظام لايمكن أن تحذف " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "الإمتيازات غير كافية " + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "حذف صورة النظام تطلب إمتيازات المدراء لتتم ." + "أنت لم تنقل أو تعرف على إمتيازات المدراء الضرورية . \n \n " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "ملحوظة " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + "تنصيب إضافات meld الحزم سوف تحسن من عملية المقارنة المرئية " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "فشل بعد الحجج " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + "إصدار المستكشف يتطلب طلبين :\n" + "- مسار مجلد صورة نظام حساب root" + "- إسم الملف للإستعراض " + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "إختر " + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "خدمة إدارة صورة النظام تعتمد خطأ " + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + "خدمة إدارة صورة النظام تكون بحالة غير فعالة ويستحق الإعتمادية. اخطاء الإرتباطات التالية ممكن إيجادها:\n" + "\n" + "%s\n" + "\n" + "شغل الأمر التالي \"svcs -xv\" من خلال موجه الأوامر لمزيد من المعلومات حول أخطاء الإعتمادية." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "خطأ خدمة إدارة صورة النظام " + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "خدمة إدارة صورة النظام تواجه المشاكل ولذلك هي غير فعالة حتى تحل المشكلة .\n " + "\n " + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "خدمة إدارة صورة التظام لا تظهر على أنها منصبة ضمن النظام . \n " + "إنظر الى svcs(1) man page for more information." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "خدمة إدارة صورة النظام تحتاج الى صلاحيات المدير لكي تعمل , وأنت لم تعرف على أساس الإمتيازات الضرورية كمدير . \n " + "\n " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "خطأ برموز إسم صورة النظام " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "خطأ " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + "الرموز المتاحة بإسم صورة النظام :\n" + "[a-z][A-Z][0-9][-_.:\n" + "وكل الرموز الغير متاحة قد أزيلت \n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "الخطأ حدث عند إنشاء صورة النظام " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "صورة النظام قد أنشأت بنجاح " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "نجاح " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + "الصورة ضمن نظام ملفات ZFS %(zfs_fs)s\n" + "مسماه %(valid_name)s\n" + "قد أنشأ .\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + "صورة النظام تحتاج طلبين : \n" + "- مسار المجلد الخاص بصور النظام .\n" + "- نظام ملفات zfs متطابق مع مع المجلد " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "صورة النظام الأن تحتاج الى صلاحيات مدير للعمل , وأنت لم تعرف على أساس الإمتيازات الضرورية كمدير . \n \n" + "راجع مدير النظام " diff --git a/po/ca.gmo b/po/ca.gmo new file mode 100644 index 0000000000000000000000000000000000000000..b6cef3e4de6c8da6da508ce0924991e38ef15c1b GIT binary patch literal 12357 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 +# +msgid "" +msgstr "" + "Project-Id-Version: time-slider HEAD\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2009-12-07 22:37+0900\n" + "PO-Revision-Date: 2010-01-20 20:31+0100\n" + "Last-Translator: Robert Antoni Buj\n" + "Language-Team: Catalan language team \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "Configurar el sistema per a fer instantànies automàtiques de les vostres dades" + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d instantànies a ser eliminades." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Resum" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "De-_seleccionar-ho Tot" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Eliminar instantànies" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Eliminant Instantànies" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Eliminant Instantànies..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Sortir quant estigui fet. " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Sistema de Fitxers:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Nom:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Premeu a Eliminar per a continuar." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Escanejant instantànies...." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Seleccioneu les instantànies ZFS a eliminar del següent llistat." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Activar Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Sistema de fitxers a fer Còpia de seguretat" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Opcions avançades" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "_Personalitzat:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "Configurant Time Slider..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Per a usuaris avançats" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Recomanat per a la majoria d'usuaris" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Reduir les còpies de seguretat quan l'espai d'emmagatzemament excedeixi:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Gestor de Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "Time Slider fa còpies de seguretat regularment prenent Instantànies ZFS programades" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Tots" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Eliminar Instantànies..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "de la capacitat del sistema de fitxers" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Fer una instantània ara" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Fer una instantània de " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "amb el nom :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Dl" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Versió Actual" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Versions Anteriors" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Doble clic per a obrir un fitxer" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Obrir la versió actual del fitxer" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Escanejant per a versions antigues" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Explorador de Versions de Fitxer de Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "comparar" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "nomfitxer" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "tancar-gtk" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "última modificació : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "nom :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "mida : " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "Time Slider: Advertència poc espai" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Emergència: '%s' està ple!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "El sistema de fitxers: '%s', sobreexcedeix %s%%." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + "El sistema de fitxers: '%s', està gairebé %s%% ple.\n" + "Com a mesura d'emergència, Time Slider ha destruït totes les vostres còpies de seguretat.\n" + "Per solucionar aquest problema, elimineu qualsevol fitxer innecessari de '%s', o afegiu espai al disc (vegeu la documentació de ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Emergència: '%s' gairebé està ple!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "El sistema de fitxers: '%s', excedeix %s%% de la seva capacitat total" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + "El sistema de fitxers: '%s', excedeix %s%% de la seva capacitat total. Com a mesura d'emergència, Time Slider ha moltes o totes les seves còpies de seguretat per a prevenir que el disc estigués ple. Per a prevenir que això no torni a passar, elimineu qualsevol fitxer innecessari '%s', o afegiu espai de disc (vegeu la documentació de ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Urgent: '%s' gairebé està ple!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + "El sistema de fitxers: '%s', excedeix %s%% de la seva capacitat total. com a mesura correctora, Time Slider ha destruït algunes còpies de seguretat, i en destruirà més, eventualment totes, si la capacitat continua disminuint.\n" + "Per preveure que això no torni a passar, elimineu qualsevol fitxer innecessari de '%s', o afegiu espai al disc (vegeu la documentació de ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Avís: '%s' s'està emplenant" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + "'%s' excedeix %s%% de la seva capacitat total. Per a corregir-ho, Time Slider ha algunes còpies de seguretat recents, i en continuarà destruint-ne més si la capacitat continua reduint-se.\n" + "Per a prevenir que això no torni a passar més, elimineu qualsevol fitxer innecessari en '%s', o afegiu espai de disc addicional (vegeu la documentació de ZFS).\n" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "Detalls..." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Llegat" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Punt de Muntatge" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Nom Sistema de Fitxers" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Nom de la instantània" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Temps de creació" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Tots" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Mensual" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Setmanal" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Diària" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Cada hora" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Cada 1/4 d'hora" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "La instantània no pot ser eliminada" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + "%s té un o més clons que en depenen i no serà eliminat. Per eliminar aquesta" + "instantània, primer elimineu tots els datasets i instantànies clonades d'aquesta instantània." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 instantània serà eliminada." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "No s'han pogut llegir algunes instantànies" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "No s'han pogut eliminar algunes instantànies" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Privilegis insuficients" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "L'eliminació de instantànies requereix privilegis administratius per a funcionar. No teniu assignats els privilegis necessaris administratius.\n" + "\n" + "Consulteu al vostre administrador de sistema " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "Pista" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + "Instal·lant el paquet addicional meld enriquirà la comparació de fitxers de virtualització" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Nombre d'arguments incorrectes." + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + "L'explorador de versions requereix 2 arguments :\n" + "- La ruta del directori arrel de la instantània.\n" + "- El nom del fitxer a explorar." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Seleccionar" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Error de dependència del servei gestor de instantànies" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + "El servei gestor de instantànies s'ha posat fora de línia a causa del " + "problema de dependència. S'han trobat els següents problemes de dependència:\n" + "\n" + "%s\n" + "\n" + "Executeu \"svcs -xv\" des de la línia de comandes per a més informació quant a aquests problemes de dependència." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "S'ha produït un error al servei de gestió de instantànies" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "El servei gestor de instantànies ha trobat un problema i ha estat " + "deshabilitat mentre no es solucioni el problema.\n" + "\n" + "Vegeu la pàgina del manual svcs(1) per a més informació." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "El servei gestor de instantànies no sembla que estigui instal·lat en aquest" + "sistema.\n" + "\n" + "Vegeu la pàgina del manual svcs(1) per a més informació." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "El servei gestor de instantànies requereix privilegis d'administrador per a funcionar. No teniu assignats els privilegis necessaris administratius.\n" + "\n" + "Consulteu al vostre administrador de sistema " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Caràcters no vàlids al nom de la instantània" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Error" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + "Els caràcters permesos pel nom de la instantània són :\n" + "[a-z][A-Z][0-9][-_.:\n" + "S'eliminaran tots els caràcters incorrectes\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "S'ha produït un error mentre s'estava creant la instantània" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Instantània correctament creada" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Realitzat" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + "Una instantània del sistema de fitxers zfs %(zfs_fs)s\n" + "anomenada %(valid_name)s\n" + "ha estat creada.\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + "Instantània Ara requereix 2 arguments :\n" + "- La ruta del directori per fer la instantània.\n" + "- El sistema de fitxers zfs corresponent al directori." + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "La instantània Ara requereix privilegis administratius per a funcionar. Sembla no teniu assignats els privilegis necessaris administratius.\n" + "\n" + "Consulteu al vostre administrador de sistema " diff --git a/po/cs.gmo b/po/cs.gmo new file mode 100644 index 0000000000000000000000000000000000000000..3508940502da21ed997ef07a3cdd21785044a292 GIT binary patch literal 11697 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 literal 0 HcmV?d00001 diff --git a/po/cs.po b/po/cs.po new file mode 100644 index 0000000..2439cb3 --- /dev/null +++ b/po/cs.po @@ -0,0 +1,541 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" + "Project-Id-Version: time-slider HEAD\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2009-12-07 22:37+0900\n" + "PO-Revision-Date: 2010-01-20 20:31+0100\n" + "Last-Translator: Kristyna Kitzbergerova\n" + "Language-Team: Czech language team \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "Nakonfigurujte si systém tak, aby vytvářel automatické snímky VaÅ¡ich dat" + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d snímky budou smazány." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Shrnutí" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "V_yřadit vÅ¡e" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Smazat snímky" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Mažu snímky" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Mažu snímky..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Ukončit po dokončení. " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Souborový systém:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Jméno:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Stiskněte Smazat pro pokračování." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Skenuji snímky..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Vyberte ZFS snímek ze seznamu, který má být smazán." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Povolit Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Záloha souborového systému" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Rozšířené možnosti" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "V_lastní" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "Konfiguruji Time Slider..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Pro pokročilé uživatele" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Doporučeno pro větÅ¡inu uživatelů" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "ZmenÅ¡it zálohy pokud úložný prostor překročí:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Správce pro Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "Time Slider zálohuje pravidelně data pomocí časových ZFS smínků" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_VÅ¡e" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Smazat snímek..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "kapacity souborového systému" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Pořídit snímek nyní" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Pořídit snímek " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "s názvem:" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "Pondělí 10" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Současná verze" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Starší verze" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Dvojitým kliknutím na soubor tento soubor otevřete" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Otevřete soubor současné verze" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Skenuji pro starší verze" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Time Slider - Průzkumník verzí " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "porovnat" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "jméno souboru" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "Naposledy změněn : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "jméno :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "velikost : " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "Time Slider: Varování - málo místa" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Stav nouze: '%s' je plný!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "Souborový systém: %s', je z %s%% plný." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + "Souborový systém: '%s', z %s%% je přeplněn.\n" + "Time Slider smazal vÅ¡echny své zálohy jako nouzové opatření.\n" + "Chcete-li vyřeÅ¡it tento problém, smažte nepotřebné soubory na '%s' nebo " + "přidejte více místa (viz ZFS dokumentace)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Stav nouze: '%s' je skoro plný!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "Souborový systém: '%s', překročil %s%% své celkové kapacity" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + "Souborový systém '%s', překročil %s%% ze své celkové kapacity. Jako " + "nouzové opatření Time Slider odstranil větÅ¡inu svých záloh, aby předeÅ¡el" + " zaplnění disku. Smažte nepotřebné soubory na '%s' nebo přidejte místo, " + "abyste se tomuto problému příště vyhnuli (viz ZFS dokumentace)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Naléhavé: '%s' je skoro plný!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + "Souborový systém '%s', překročil %s%% ze své celkové kapacity. " + "Jako nouzové opatření, Time Slider odstranil některé ze svých záloh a odstraní více, eventuálně vÅ¡e, pokud se kapacita bude nadále snižovat.\n" + "Abyste tomuto zabránili do budoucna, smažte nepotřebné soubory na '%s', " + "nebo přidejte další diskový prostor (více v dokumentaci o ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Upozornění: '% s' se zaplňuje" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + "'%s', překročil %s%% ze své celkové kapacity. Time Slider odstranil některé své zálohy a odstraní více, pokud se kapacita bude nadále snižovat.\n" + "Abyste tomuto zabránili do budoucna, smažte nepotřebné soubory na '%s', nebo přidejte další diskový prostor (více v dokumentaci o ZFS).\n" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "Podrobnosti..." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Odkaz" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Místo připojení" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Jméno souborového systému" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Jméno snímku" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Čas vytvoření" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "VÅ¡e" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Měsíčně" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Týdně" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Denně" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Každou hodinu" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Každou 1/4 hodinu" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Snímek nemůže být smazán" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + "%s má jednu nebo více závislých klonů a nemůže být smazán." + " Abyste mohli odstranit tento snímek, musíte nejdříve smazat vÅ¡echny " + "jeho datové sady a klony." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 snímek bude smazán" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Některé snímky nelze přečíst" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Některé snímky nemohou být smazány" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Nedostatečná práva" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Smazání snímku požaduje administrátorská práva. Vy tato práva nemáte.\n" + "Kontaktujte VaÅ¡eho správce systému " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "Nápověda" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + "Instalování volitelných balíčků zvýrazní vizualizaci srovnání souborů" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Neplatný počet argumentů." + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + "Průzkumník verzí požaduje 2 argumenty: \n" + "- Cestu k adresáři se snímky kořenového adresáře" + "- Jméno souboru, které se má prozkoumat" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Vybrat" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Chyba v závislostech ve službě Správce snímků" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + "Služba Správce snímků byla umístěna offline kvůli problémům v závislostech. " + "Následující chyby v závislostech byly nalezeny: \n" + "\n" + "%s\n" + "\n" + "Z příkazové řádky spusÅ¥te \"svcs -xv\" pro více informací o těchto potížích." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Chyba ve službě Správce snímků" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "Služba Správce snímku zaznamenala problém a byla proto vypnuta, " + "dokud problém nebude vyřeÅ¡en. \n" + "\n" + "Viz manuálové stránky svcs(1) pro více informací." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "Služba Správce snímků se nezdá, že by byla nainstalována na tomto " + "systému. \n" + "\n" + "Viz manuálové stránky svcs(1) pro více informací." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Služba Správce snímků požaduje administrátorská práva. " + "Vy tato práva nemáte.\n" + "\n" + "Kontaktujte VaÅ¡eho správce systému " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Neplatný znak v názvu snímku" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Chyba" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + "Povolené znaky pro název snímku jsou následující: \n" + "[a-z][A-Z][0-9][-_.:\n" + "VÅ¡echny neplatné znaky budou odstraněny\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Vyskytla se chyba při vytváření snímku" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Snímek byl vytvořen úspěšně" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Úspěch" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + "Byl vytvořen snímek ZFS souborového systému%(zfs_fs)s\n" + "s názvem %(valid_name)s\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + "Snímek nyní požaduje 2 argumenty: \n" + "- Cestu k adresáři, kde má být snímek vytvořen" + "- zfs souborový systém odpovídající tomuto adresáři" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Snímek nyní vyžaduje administrátorská práva. Vy tato práva nemáte. \n" + "\n" + "Kontaktujte VaÅ¡eho správce systému " diff --git a/po/de.gmo b/po/de.gmo new file mode 100644 index 0000000000000000000000000000000000000000..4b22606041b584d9d1eda385b39ae405934e392a GIT binary patch literal 10630 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 +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2009-02-16 18:55+0100\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "System für automatische Snapshots Ihrer Daten konfigurieren" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Zeit-Schieberegler" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d Schnappschüsse werden gelöscht." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Zusammenfassung" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "A_uswahl aufheben" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Schnappschüsse löschen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Löschen von Schnappschüssen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Schnappschüsse werden gelöscht..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Beenden Sie die Anwendung, wenn Sie fertig sind. " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Dateisystem:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Name:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Klicken Sie auf „Löschen“, um fortzufahren." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Suche nach Schnappschüssen wird ausgeführt..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "" +"Wählen Sie aus der nachstehenden Liste die zu löschenden ZFS-Schnappschüsse " +"aus." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Zeit-Schieberegler aktivieren" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Dateisystem zur Sicherung" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Erweiterte Optionen" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "B_enutzerdefiniert:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Für erfahrene Benutzer" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Empfohlen für die meisten Benutzer" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "" +"Sicherungen reduzieren, wenn Speicherplatznutzung Folgendes überschreitet:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Zeit-Schieberegler-Manager" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" +"Der Zeit-Schieberegler sichert regelmäßig Daten durch Erfassung von zeitlich " +"festgelegten ZFS-Schnappschüssen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Alle" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Schnappschüsse löschen..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "der Dateisystemkapazität" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Jetzt einen Schnappschuss nehmen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Einen Schnappschuss nehmen von " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "mit dem Namen:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Aktuelle Version" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Ältere Versionen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Auf die Datei doppelklicken, um sie zu öffnen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Die aktuelle Version der Datei öffnen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Nach älteren Versionen suchen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Time Slider File Version Explorer" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "vergleichen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "Dateiname" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "Letzte Änderung: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "Name:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "Größe: " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "Zeit-Schieberegler-Manager" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Notfall: '%s' ist voll!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"Das Dateisystem: '%s', ist über %s%% voll.\n" +"Der Zeit-Schieberegler hat im Zuge einer Notfallmaßnahme alle " +"Sicherungsdateien gelöscht.\n" +"Um dieses Problem zu beheben, löschen Sie alle nicht benötigten Dateien auf " +"'%s', oder fügen Sie Speicherplatz hinzu (siehe ZFS-Dokumentation)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Notfall: '%s' ist fast voll!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"Das Dateisystem: '%s', %s%% der Gesamtkapazität überschritten. Der Zeit-" +"Schieberegler hat im Zuge einer Notfallmaßnahme die meisten oder alle " +"Sicherungsdateien gelöscht, um zu verhindern, dass der Datenträger voll " +"wird. Um zu verhindern, dass dieses Problem erneut auftritt, löschen Sie " +"alle nicht benötigten Dateien auf '%s', oder fügen Sie Speicherplatz hinzu " +"(siehe ZFS-Dokumentation).\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Dringend: '%s' ist fast voll!" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"Das Dateisystem: '%s', %s%% der Gesamtkapazität überschritten. Der Zeit-" +"Schieberegler hat im Zuge einer Abhilfemaßnahme einige Sicherungsdateien " +"gelöscht und wird weitere und letztlich alle Sicherungsdateien löschen, wenn " +"die Kapazität weiter abnimmt.\n" +"Um zu verhindern, dass dieses Problem erneut auftritt, löschen Sie alle " +"nicht benötigten Dateien auf '%s', oder fügen Sie Speicherplatz hinzu (siehe " +"ZFS-Dokumentation)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Warnung: '%s' wird voll" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"'%s' %s%% der Gesamtkapazität überschritten. Um dieses Problem zu beheben, " +"hat der Zeit-Schieberegler einige der zuletzt erstellten Sicherungsdateien " +"gelöscht und wird weitere Sicherungsdateien löschen, wenn die Kapazität " +"weiter abnimmt.\n" +"Um zu verhindern, dass dieses Problem erneut auftritt, löschen Sie alle " +"nicht benötigten Dateien auf '%s', oder fügen Sie Speicherplatz hinzu (siehe " +"ZFS-Dokumentation)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Veraltet" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Einhängepunkt" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Dateisystemname" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Schnappschussname" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Erstellungszeit" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Alle" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Monatlich" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Wöchentlich" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Täglich" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Stündlich" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Viertelstündlich" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Schnappschuss kann nicht gelöscht werden" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s hat einen oder mehrere abhängige Klone und wird nicht gelöscht. Um " +"diesen Schnappschuss zu löschen, löschen Sie zunächst alle Datensätze und " +"Schnappschüsse, die aus diesem Schnappschuss geklont wurden." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 Schnappschuss wird gelöscht." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Einige Schnappschüsse konnten nicht gelesen werden" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Einige Schnappschüsse konnten nicht gelöscht werden" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Nicht ausreichend Zugriffsrechte" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Die Löschung von Schnappschüssen erfordert Administratorrechte. Ihnen " +"wurden nicht die nötigen Administratorrechte zugewiesen.\n" +"\n" +"Wenden Sie sich an Ihren Systemadministrator. " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Ungültige Argumente." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"Versions-Explorer erfordert 2 Argumente:\n" +"- Pfad des Schnappschuss-Stammverzeichnisses.\n" +"- Dateiname, nach dem gesucht werden soll." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Auswählen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "" +"Abhängigkeitsfehler im Zusammenhang mit dem Schnappschussverwaltungsdienst" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"Der Schnappschussverwaltungsdienst wurde wegen eines Abhängigkeitsproblems " +"offline geschaltet. Folgende Abhängigkeitsprobleme wurden festgestellt:\n" +"\n" +"%s\n" +"\n" +"Führen Sie über eine Eingabeaufforderung den Befehl \"svcs -xv\" aus, um " +"weitere Informationen zu diesen Abhängigkeitsproblemen zu erhalten." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Schnappschussverwaltungsdienst-Fehler" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"Der Schnappschussverwaltungsdienst ist von einem Problem betroffen und wurde " +"bis zur Behebung des Problems deaktiviert.\n" +"\n" +"Weitere Informationen finden Sie in der Online-Dokumentation, svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"Der Schnappschussverwaltungsdienst ist offenbar nicht auf diesem System " +"installiert.\n" +"\n" +"Weitere Informationen finden Sie in der Online-Dokumentation, svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Für die Ausführung des Schnappschussverwaltungsdienstes sind " +"Administratorrechte erforderlich. Ihnen wurden nicht die nötigen " +"Administratorrechte zugewiesen.\n" +"\n" +"Wenden Sie sich an den Systemadministrator " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Schnappschussname enthält ungültige Zeichen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Fehler" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"Folgende Zeichen sind für den Schnappschussnamen zulässig:\n" +"[a-z][A-Z][0-9][-_.:\n" +"Alle ungültigen Zeichen werden entfernt\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Fehler beim Erstellen des Schnappschusses" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Schnappschuss wurde erfolgreich erstellt" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Erfolg" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"Ein Schnappschuss des ZFS-Dateisystems %(zfs_fs)s\n" +"mit dem Namen %(valid_name)s\n" +"wurde erstellt.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"„Schnappschuss jetzt“ erfordert 2 Argumente:\n" +"- Pfad des Verzeichnisses, von dem ein Schnappschuss genommen werden soll.\n" +"- Entsprechendes ZFS-Dateisystem für dieses Verzeichnis." + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"\"Schnappschuss jetzt\" erfordert Administratorrechte. Ihnen wurden nicht " +"die nötigen Administratorrechte zugewiesen.\n" +"\n" +"Wenden Sie sich an Ihren Systemadministrator. " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "" +#~ "A snapshot of zfs filesystem %s\n" +#~ "named %s\n" +#~ "has been created.\n" +#~ msgstr "" +#~ "Ein Schnappschuss des ZFS-Dateisystems %s\n" +#~ "mit dem Namen %s\n" +#~ "wurde erstellt.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "" +#~ "Älteste Sicherungsdateien löschen, wenn der zur Nutzung vorgesehene " +#~ "Speicherplatz überschritten wird:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "der Pool-Kapazität" diff --git a/po/es.gmo b/po/es.gmo new file mode 100644 index 0000000000000000000000000000000000000000..df2a1721072c7350b884bf800c5ef11a9ede60d8 GIT binary patch literal 10670 zcmd6sZHyh)S;tS)^zA00kR}k?q&ace#;NyS@2-Or-@G(ydmS6R_Qtz*L{e*YcIMoB z$DTWLJ!fXU*J=d_b6^jA5#*Kn68Jp$W$-@mpDX+?P}aQ{BINt~!TZ6-L0RW0D0H6%9|f;~4}qTr z<-0e)C&4d)^8NQK{0Hz}?*9#x`R=+$srP~pfXSw0~Ef#fRIJ*-v))we*`sn&j(97XTc9~|5Syi!Drv4)F;8e=J`#8@+mO= z38nrX{2?g%e-okn5oq}s($xbGKntirneRBb2wnoko^FDD@T;KE`8Z4pe~aMV;AxOo zY8e#yZGfWpDIt^{x*o{)NSxG_zh6(;=?FY=4pbWmm{Ez`v&+j z_+3!=dIV+5x(lH2a|)F8RzRVv3(7j50iOVGfwSOuK+)g7gR;(p43hhgfHGgZx<3cX zI(-n<)Gvdw&hLT;z%PQL|8Idu!G8ipZU<0~@c(g8OWd4t@(1I{y`9Y4x8VB2f=vbianakARPXe+IL^&+{L^?4#WO3X6T0`!`VPUx6u$ zikub@@^j!zpwRKFUMxe}d4g3K3Iw<3R4}?trjHT6GoU5E7^ZNLCezdC_#H1EDVaL<+EO?1i zV#q~KInHv5?BrN3!N)4miyWdap;73cJVY-)%PD%3L+E>v^9U#Gdp!tGqNmfGa(sdl z7Q77Qeo-zQM@vwLDy-o+KMrt;uM(pCae=G&%Dc;3f1T(0Jm*Qy6;9E)*o7P-Z#l&O z>f@#Q2lx!9*y>@AV-Y(K55T6J;*1-x-z+ zO>Ufp0GVq{j$a_uohJXG^@dO20|#}3cUAD%m=p4)ig zM3H4e{3V;YC{3O_yzzoOUP{bHZ1q~yw|X^>LYviZPDMOl9lG51ov?J=1XuONLH+uC z%-DK1S3g}X`hAlP{q5qI)U=~-bR7K0*7+Ir%t0QkcifY%k(j;>d9Z0%e_if)DAM1s zHqk+5ji{ud7Q;=G1Pnbt$l<`L#W81@E>co2C^0ePpC(@)7bqi+9dsbKM zROx#~0s;2gW}iFJ702xVRp6~St0E6N$b+97_@(rUtz^f~k~`Jk;%F|Dap zTR1eRDkDCVxT4dEf=KLkC5twrIOkC)0#H#z~%5AaUe($vKypH=p;D%8?Z0SX!BMM<>ig@0)#u4{seblb@ zNuMBu78^{5Oy-38%C%?5xj{9+rw}Z?Nw_Ku>L>Qb3_}#vlA{n(I%?$YyVq*0rC7KB zu5=Bb6!gr1C?y_ax+Y3yYsWUpP#4L_w#yBdEXGItSxIF~7~);-Y87w}I%|EH2-CpF zQolJp+1gKb_$J$7R*FvSxYZCzB^pVUyUU%(3f^nER}%AlcA~A4$3d2|k@f$N*s9G? z?=;mgC4NY_5b2t%ieXXWFpU@$BgZnK^C=ARVHM#?FmaoKJ2?NOOh`B+f|r4x6d_9Z zDG5QElrr6S@lnDvh}j8Ptka1j($BD9jTB>?aJfo8Egye7siZ(+3%f_K$yli9u*rq( zMq|b`8W)O09}t3db8GWJxfK|UkGzc>l$rYjG6KJGP1CQ;ubcA!iWqkX{E>ag zb_+=%3{@X`Um08D}Ti4Z_rE5CMIu_sLI8>0}dTy%pvLaXZ$ zDUZ)0gx0oN$CFD;&IT(E;Q%bUxt8->l|Mb|ag(zb4_ zoL_6!MM3iX zGJj}po^fYks=1b##KneGtW8*OSMzkx#I=ILxfi7@a4AL*xmKEW`5@^QM6KqUHT}0A zzIt{Biyg}NkZ7e&V$+mALdL|@(5`2@KJ9c1SmgiIhB+3cpjqY#XB3CR16g(zOogNas zNm2Bmu;F;BmR_MY9L=oRvdz@Qvywlue@T8!(z-!P>Z89*=?ckKk}HHG?wFO5ogWg} zCn%dBf{b;%^!6=1KQ*$YPMDBzTbK+JXSyEg{~p!4H>2&ZkhBJpFVyArX|q8MltUd# zjiVcIEk7|f+aIL$s8teU+b6f+jmd&uv{{ml&FgDyCp|Wp46X)|>?#P1YBmZ@SbtyH zFqA(85Q^!tdl&!551U7%aO@q?aIZc)|Q-qqTr8X(-$f1DEU!b579`fms0MZ z9|V}X5JA}wr^czZ6qTs6un|<%KCcHh>qmJc$|A`Z9+R}cA=L`J;o(#AN1KbZ&g~~8ZXj|Mc*|;%)b|&ON>@kEhx*|IWfjYKa z%Uj)0Ge3l$R3;RuqivVB4KLUiU;&2)V;Of!tjQwLYIu;@%t{m>ckZsy~Oce+tPkvJzt#ey!yB?mM+ zh1tRh{V@h4<(B7#RuoZ*{a2bjXQ{7%++^3O4k_w-YX7B zZ$yG2fz(cH4y6KU6cj>8*lv0+Wy;My*~)<#UGY%dU~Cpspnla9$*s>*`r!hW6p zcnQRw8sxKydsP8rhbgr9erf**JJCw^MV|gUki8dQm6emf zPVYCBw9u#Y*(48>0QjSo&>g#+Y_~L}CCWi|oi4^=#F8QiCsj^E1H@ZJxNHh$JgL9I_N^}Yk%*gG*l(&_>!wwy$2+I8baQr2pCUN^zrtE}%cmQnXdJE3aKARAkfp$Y7+!adYl1 zbJ5If-@YN#>?)qC1awrZki3=ch1!ID?po17F~NWAZAOd=ChI)hjg7%`fegwr%h9e# zj9OolVs?G+8g|^vGnpz$Sg(s3-_qkhO}yFu)0oy9MT>9i1!d7Jtvz2FwiSGhc2{9? VuR4*W(QN+--8m(i!yD_5{~O1j?EL@$ literal 0 HcmV?d00001 diff --git a/po/es.po b/po/es.po new file mode 100644 index 0000000..4e8bb4b --- /dev/null +++ b/po/es.po @@ -0,0 +1,870 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2009-02-16 18:31+0100\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "" +"Configurar el sistema para tomar instantáneas de manera automática de los " +"datos" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Deslizador de tiempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "Se eliminarán %d instantáneas." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Resumen" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "D_eseleccionar todo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Eliminar instantáneas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Eliminación de instantáneas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Eliminando instantáneas..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Salir al terminar. " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Sistema de archivos:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Nombre:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Pulse Suprimir para continuar." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Buscando instantáneas..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "" +"Seleccione las instantáneas ZFS que desea eliminar de la siguiente lista." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Habilitar deslizador de tiempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Sistemas de archivos de los que hacer copia de seguridad" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Opciones avanzadas" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "_Personalizar:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Para usuarios avanzados" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Recomendado para la mayoría de los usuarios" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "" +"Destruir las copias de seguridad cuando se sobrepase el uso del espacio de " +"almacenamiento:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Administrador del deslizador de tiempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" +"El deslizador de tiempo hace copias de seguridad de datos con regularidad " +"tomando instantáneas de ZFS programadas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Todo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Eliminar instantáneas..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "de capacidad de sistema de archivos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Tomar una instantánea ahora" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Tomar una instantánea de " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "con el nombre:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Versión actual" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Versiones anteriores" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Haga doble clic para abrir este archivo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Abrir la versión actual del archivo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Buscando versiones anteriores" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Explorador de versiones de archivos del deslizador de tiempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "comparar" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "nombre_archivo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-cerrar" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "última modificación: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "nombre:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "tamaño: " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "Administrador del deslizador de tiempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Emergencia: '%s' se ha llenado" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"El sistema de archivos: '%s', está %s%% por encima.\n" +"Como medida de emergencia, el deslizador de tiempo ha destruido todas sus " +"copias de seguridad.\n" +"Para solventar este problema, elimine todos los archivos innecesarios de '%" +"s' o agregue espacio al disco (consulte la documentación de ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Emergencia: '%s' se ha llenado casi del todo" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"El sistema de archivos: '%s', ha sobrepasado un %s%% de su capacidad total. " +"Como medida de emergencia, el deslizador de tiempo ha destruido casi todas " +"sus copias de seguridad para impedir que el disco se llenara por completo. " +"Para impedir que se repita este problema, elimine todos los archivos " +"innecesarios de '%s' o agregue espacio al disco (consulte la documentación " +"de ZFS).\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Urgente: '%s' se ha llenado casi del todo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"El sistema de archivos: '%s', ha sobrepasado un %s%% de su capacidad total. " +"Como solución, el deslizador de tiempo ha destruido algunas copias de " +"seguridad, y destruirá más, posiblemente todas, conforme siga disminuyendo " +"la capacidad.\n" +"Para impedir que se repita este problema, elimine todos los archivos " +"innecesarios de '%s' o agregue espacio al disco (consulte la documentación " +"de ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Advertencia: '%s' se está llenando" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"'%s' ha sobrepasado un %s%% de su capacidad total. Para solucionarlo, el " +"deslizador de tiempo ha destruido algunas copias de seguridad recientes, y " +"destruirá más conforme siga disminuyendo la capacidad.\n" +"Para impedir que se repita este problema, elimine todos los archivos " +"innecesarios de '%s' o agregue espacio al disco (consulte la documentación " +"de ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Antiguo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Punto de montaje" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Nombre del sistema de archivos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Nombre de instantánea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Momento de creación" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Todos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Cada mes" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Cada semana" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Cada día" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Cada hora" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Cada cuarto de hora" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "La instantánea no se puede eliminar" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s tiene uno o más duplicados y no se puede eliminar. Para eliminar esta " +"instantánea, primero debe borrar todos sus conjuntos de datos e instantáneas " +"duplicados." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "Se eliminará 1 instantánea." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "No se ha podido leer algunas instantáneas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "No se ha podido eliminar algunas instantáneas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Los privilegios no son suficientes" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"La eliminación de la instantánea necesita privilegios administrativos. No se " +"le ha asignado los correspondientes privilegios administrativos.\n" +"\n" +"Consulte al administrador del sistema" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Recuento de argumentos no válidos." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"El explorador de versiones necesita 2 argumentos:\n" +"- La ruta del directorio raíz de las instantáneas.\n" +"- El nombre del archivo que se va a explorar." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Seleccionar" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Error de dependencia del servicio de administrador de instantáneas" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"El servicio de administrador de instantáneas se ha quedado sin conexión por " +"un problema de dependencia. Se han detectado los siguientes problemas de " +"dependencia:\n" +"\n" +"%s\n" +"\n" +"Ejecute \"svcs -xv\" desde un indicador de comandos para obtener más " +"información sobre estos problemas de dependencia." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Error del servicio de administrador de instantáneas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"El servicio de administrador de instantáneas ha detectado un problema y se " +"ha desactivado hasta que se solucione el problema.\n" +"\n" +"Para obtener más información, consulte la página de comando man svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"El servicio de administrador de instantáneas parece no estar instalado en " +"este sistema.\n" +"\n" +"Para obtener más información, consulte la página de comando man svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"El servicio de administrador de instantáneas necesita privilegios " +"administrativos. No se le ha asignado los correspondientes privilegios " +"administrativos.\n" +"\n" +"Consulte al administrador del sistema " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Caracteres no válidos en el nombre de la instantánea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Error" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"Los caracteres permitidos en los nombres de las instantáneas son:\n" +"[a-z][A-Z][0-9][-_.:\n" +"Se suprimirán todos los caracteres no válidos\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Error al crear la instantánea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "La instantánea se creó correctamente" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Correcto" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"Una instantánea del sistema de archivos zfs %(zfs_fs)s\n" +"llamada %(valid_name)s\n" +"se ha creado.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"Tomar instantánea ahora necesita 2 argumentos:\n" +"- La ruta del directorio del que se va a tomar la instantánea.\n" +"- El sistema de archivos zfs correspondiente a este directorio." + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Tomar instantánea ahora necesita privilegios administrativos. No se le han " +"asignado los correspondientes privilegios administrativos.\n" +"\n" +"Consulte al administrador del sistema" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "" +#~ "A snapshot of zfs filesystem %s\n" +#~ "named %s\n" +#~ "has been created.\n" +#~ msgstr "" +#~ "Una instantánea del sistema de archivos zfs %s\n" +#~ "llamada %s\n" +#~ "se ha creado.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "" +#~ "Destruir la copias de seguridad más antiguas cuando el uso del espacio de " +#~ "almacenamiento supere:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "de capacidad de la agrupación" diff --git a/po/fr.gmo b/po/fr.gmo new file mode 100644 index 0000000000000000000000000000000000000000..13e64f77a1101c6c35a8ec932897086d5ddbc409 GIT binary patch literal 10807 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 Ap8x;= literal 0 HcmV?d00001 diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 0000000..6e01fed --- /dev/null +++ b/po/fr.po @@ -0,0 +1,864 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2009-02-16 18:36+0100\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "" +"Configurer le système de manière à prendre des instantanés automatiques des " +"données" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Curseur temporel" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d instantanés seront supprimés." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Résumé" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "Tout désél_ectionner" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Supprimer des instantanés" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Suppression des instantanés" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Suppression des instantanés..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Quitter une fois l'opération terminée. " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Système de fichiers :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Nom :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Cliquez sur Supprimer pour continuer." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Analyse des instantanés..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Sélectionnez les instantanés ZFS à supprimer dans la liste suivante." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Activer le curseur temporel" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Systèmes de fichiers à sauvegarder" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Options avancées" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "Pers_onnalisé :" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Pour les utilisateurs expérimentés" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Recommandé pour la plupart des utilisateurs" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Réduire les sauvegardes lorsque l'espace de stockage utilisé dépasse :" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Gestionnaire de curseur temporel" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" +"Le curseur temporel sauvegarde les données régulièrement en prenant des " +"instantanés ZFS différés" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Tous" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "Supprimer _des instantanés..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "de la capacité du système de fichiers" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Prenez un instantané" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Prenez un instantané de " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "nommé :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Version actuelle" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Anciennes versions" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Cliquez deux fois sur le fichier pour l'ouvrir." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Ouvrez la version actuelle du fichier" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Recherche d'anciennes versions" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Explorateur de versions du fichier de curseur temporel" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "comparaison" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "nom de fichier" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "dernière modification : " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "nom :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "taille : " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "Gestionnaire de curseur temporel" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Urgence : %s est plein !" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"Le système de fichiers %s est rempli à plus de %s %%.\n" +"Par mesure d'urgence, le curseur temporel a détruit toutes ses sauvegardes.\n" +"Pour résoudre ce problème, supprimez tout fichier non indispensable sur %s " +"ou ajoutez de l'espace disque (voir la documentation ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Urgence : %s est presque plein !" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"Le système de fichiers %s dépasse %s %% de sa capacité totale. Par mesure " +"d'urgence, le curseur temporel a détruit toutes ses sauvegardes ou la " +"plupart d'entre elles afin d'éviter que le disque soit plein. Pour éviter " +"que cela se reproduise, supprimez tout fichier non indispensable sur %s ou " +"ajoutez de l'espace disque (voir la documentation ZFS).\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Urgent : %s est presque plein !" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"Le système de fichiers %s dépasse %s %% de sa capacité totale. Pour résoudre " +"cette situation, le curseur temporel a détruit certaines sauvegardes, en " +"détruira d'autres et finira par les détruire toutes, si la capacité continue " +"de diminue.\n" +"Pour éviter que cela se reproduise, supprimez tout fichier non indispensable " +"sur %s ou ajoutez de l'espace disque (voir la documentation ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Avertissement : %s est presque plein" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"%s dépasse %s %% de sa capacité totale. Pour résoudre cette situation, le " +"curseur temporel a détruit des sauvegardes récentes et en détruira d'autres " +"si la capacité continue de diminue.\n" +"Pour éviter que cela se reproduise, supprimez tout fichier non indispensable " +"sur %s ou ajoutez de l'espace disque (voir la documentation ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Hérité" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Point de montage" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Nom de système de fichiers" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Nom de l'instantané" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Période de création" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Tout" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Mensuelle" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Hebdomadaire" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Journalière" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Toutes les heures" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Tous les quarts d'heure" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Impossible de supprimer l'instantané" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s a un ou plusieurs clones dépendants et ne sera pas supprimé. Supprimez " +"au préalable tous les jeux de données et instantanés clonés à partir de cet " +"instantané pour pouvoir supprimer ce dernier." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 instantané sera supprimé." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Certains instantanés n'ont pas pu être lus" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Certains instantanés n'ont pas pu être supprimés" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Privilèges insuffisants" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Pour supprimer des instantanés, vous devez posséder des privilèges " +"administratifs. Vous ne possédez pas les privilèges administratifs requis.\n" +"\n" +"Consultez votre administrateur système " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Nombre d'arguments incorrect." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"L'explorateur de versions nécessite 2 arguments :\n" +"- Chemin d'accès au répertoire d'instantanés racine.\n" +"- Nom de fichier à explorer." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Sélectionner" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Erreur de dépendance du service de gestionnaire d'instantanés" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"Le service de gestionnaire d'instantané a été mis hors ligne en raison d'un " +"problème de dépendance. Les problèmes de dépendance suivants ont été " +"détectés :\n" +"\n" +"%s\n" +"\n" +"Exécutez \"svcs -xv\" à partir d'une invite de commande pour plus " +"d'informations sur ces problèmes de dépendance." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Erreur du service de gestionnaire d'instantanés" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"Le service de gestionnaire d'instantanés a rencontré un problème et a été " +"désactivé jusqu'à la résolution du problème.\n" +"\n" +"Pour de plus amples informations, reportez-vous à la page de manuel svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"Le service de gestionnaire d'instantanés ne semble pas être installé sur ce " +"système.\n" +"\n" +"Pour de plus amples informations, reportez-vous à la page de manuel svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Pour exécuter le service de gestionnaire d'instantanés, vous devez posséder " +"des privilèges administratifs. Vous ne possédez pas les privilèges " +"administratifs requis.\n" +"\n" +"Consultez votre administrateur système " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Le nom de l'instantané contient des caractères non autorisés" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Erreur" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"Les caractères autorisés dans les noms d'instantané sont les suivants :\n" +"[a-z][A-Z][0-9][-_.:\n" +"Tous les caractères non autorisés seront supprimés\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Une erreur s'est produite lors de la création de l'instantané" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "L'instantané a été créé" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Terminé sans erreur" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"Un instantané d'un système de fichiers zfs %(zfs_fs)s\n" +"nommé %(valid_name)s\n" +"a été créé.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"L'option Prendre un instantané nécessite 2 arguments :\n" +"- Chemin d'accès au répertoire dont un instantané doit être pris.\n" +"- Système de fichiers ZFS correspondant à ce répertoire." + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Pour utiliser l'option Prendre un instantané, vous devez posséder des " +"privilèges administratifs. Vous ne possédez pas les privilèges " +"administratifs requis.\n" +"\n" +"Consultez votre administrateur système " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "" +#~ "A snapshot of zfs filesystem %s\n" +#~ "named %s\n" +#~ "has been created.\n" +#~ msgstr "" +#~ "Un instantané d'un système de fichiers zfs %s\n" +#~ "nommé %s\n" +#~ "a été créé.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "" +#~ "Détruire les plus anciennes sauvegardes lorsque l'espace de stockage " +#~ "utilisé dépasse :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "de la capacité du pool" diff --git a/po/hu.gmo b/po/hu.gmo new file mode 100644 index 0000000000000000000000000000000000000000..96ffccda9daac6f833515a51a974ec06f7b2f849 GIT binary patch literal 12415 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 dl;R>j*#CNmTeAt5BGrqC&(alVx)jn)^ +# +msgid "" +msgstr "" + "Project-Id-Version: time-slider HEAD\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2009-12-07 22:37+0900\n" + "PO-Revision-Date: 2010-01-20 20:31+0100\n" + "Last-Translator: Zsolt Suri \n" + "Language-Team: Hungarian language team \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "A rendszer beállítása arra, hogy automatikusan mentse az Ön adatait" + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d pillanatkép törölve lesz." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Összesítés" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "Kijelölések megszüntetése" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Pillanatképek törlése" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Pillanatképek törlése" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Pillanatképek törlése..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Kilépés, ha elkészült. " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Fájlrendszer:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Név:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Nyomjon Delete-t a folytatáshoz." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Pillanatképek keresése..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Jelöljön ki ZFS pillanatképeket törlésre a listából." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Time Slider engedélyezése" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Fájl Rendszer Mentése" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Szakértői beállítások" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "_Egyedi:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "Time Slider konfigurációja..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Tapasztalt felhasználóknak" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Ajánlott az átlagfelhasználóknak" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Csökkentse a mentések számát ha a tároló helyhasználata " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Time Slider Vezérlő" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "Time Slider rendszeresen végez mentéseket időzített ZFS pillanatképek készítésével." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Mind" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Pillanatkép törlése..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "fájlrendszer kapacitásának" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Pillanatkép készítése most" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Pillanatkép készítése a " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "névvel :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 megabájt" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Jelenlegi Verzió" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Régebbi Verziók" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Dupla kattintás a fájl megnyitásához" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Az aktuális fájl verzió megnyitása" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Keresés régebbi verziók után" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Time Slider Fájl Verzió Intéző" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "összehasonlítás" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "fájlnév" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "Utoljára módosítva : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "név :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "méret : " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "Time Slider: Kevés Tárhely Figyelmeztetés" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Vészhelyzet: '%s' megtelt!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "A fájlrendszer: '%s' több mint %s%%-kal megtelve." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + "A fájlrendszer: '%s' több mint %s%%-a megtelt.\n" + "A katasztrófa elkerülés érdekében a Time Slider törölte az összes mentést.\n" + "A probléma megoldása érdekében töröljön minden felesleges fájlt a '%s'-ről, vagy adjon hozzá több lemezterületet (Lásd ZFS dokumentációt)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Vészhelyzet: '%s' majdnem teli!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "A fájlrendszer: '%s' túllépte teljes kapacitásának %s%%-át" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + "A fájlrendszer: '%s', túllépte teljes kapacitása %s%%-át. Mint egy vészhelyzetet elkerülendő, a Time Slider megsemmisítette a legtöbb vagy minden mentést mielőtt a lemez megtelne. A jövőben az ilyen helyzeteket megelőzvén törölje a felesleges fájlokat a '%s'-en vagy adjon hozzá lemezterületet (lást ZFS dokumentációt). " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Sürgős: '%s' majdnem megtelt!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + "A fájlrendszer: '%s', túllépte teljes kapacitása %s%%-át. Mint egy javító intézkedés, a Time Slider megsemmisített számos mentést, és még többet fog, végül mindet, ahogy a kapacitás továbbra is csökken.\n" + "A jövőben az ilyen helyzeteket megelőzvén törölje a felesleges fájlokat a '%s'-en vagy adjon hozzá lemezterületet (lást ZFS dokumentációt). " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Figyelmeztetés: '%s' megtelik" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + "A '%s' meghaladta teljes kapacitása %s%%-át. Javításként, a Time Slider megsemmisített néhány közeli mentést, és még többet fog, ahogy a kapacitás továbbra is csökken.\n" + "A jövőben az ilyen helyzeteket megelőzvén törölje a felesleges fájlokat a '%s'-en vagy adjon hozzá lemezterületet (lást ZFS dokumentációt).\n" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "Részletek..." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Hagyományos" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Felcsatolási pont" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Fájlrendszer Neve" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Pillanatkép neve" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Készítés ideje" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Mind" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Havi" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Heti" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Napi" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Óránkénti" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Negyed óránkénti" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Nem törölhető pillanatkép" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + "Egy vagy több klónnal rendelkezik %s, így nem törölhető. Ezen pillanatkép törléséhez, először törölni kell a pillanatképből az összes adathalmazt és a klónozott pillanatképeket." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 pillanatkép kerül törlésre." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Néhány pillanatkép nem olvasható" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Néhány pillanatkép nem törölhető" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Nem megfelelő jogosultságok" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Pillanatkép törléséhez adminisztratív jogosultságok szükségesek. Ön nem rendelkezik a szükséges adminisztratív jogosultságokkal.\n" + "\n" + "Lépjen kapcsolatba a rendszer adminisztrátorával. " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "Javaslat" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + "Opcionális csomag telepítésével növelheti a fájlok összehasonlításának megjelenítését." + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Érvénytelen argumentum szám." + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + "A verzió kezelő kettő paramétert igényel :\n" + "- Az elérési útvonala a gyökér pillanatképnek.\n" + "- Az érintett fájl neve." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Kiválasztás" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Pillanatkép kezelő szolgáltatás függőségi hiba" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + "A pillanatkép kezelő szolgáltatás üzemen kívül lett helyezve függőségi probléma miatt. A következő függőségi problémák merültek fel:\n" + "\n" + "%s\n" + "\n" + "Futtassa a \"svcs -xv\" parancsot további információkért a függőségi problémákról." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Pillanatkép kezelő szolgáltatás hiba" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "A pillanatkép kezelő szolgáltatás problémába ütközött és ezért üzemen kívül lett helyezve a probléma megoldásáig.\n" + "\n" + "Lásd a svcs(1) man oldalt további információkért." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "A pillanatkép kezelő szolgáltatás nem úgy tűnik mint ha telepítve lenne ezen a rendszeren.\n" + "\n" + "Lásd a svcs(1) man oldalt további információkért." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "A pillanatkép kezelő szolgáltatás futtatásához adminisztratív jogosultságok szükségesek.Ön nem rendelkezik a szükséges adminisztratív jogosultságokkal.\n" + "\n" + "Lépjen kapcsolatba a rendszer adminisztrátorával. " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Érvénytelen karakterek a pillanatkép nevében" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Hiba" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + "Pillanatkép nevében a következő karakterek engedélyezettek :\n" + "[a-z][A-Z][0-9][-_.:\n" + "Minden érvénytelen karakter eltávolításra kerül\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Hiba lépett fel pillanatkép készítésekor" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Pillanatkép sikeresen elkészítve" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Eredmény" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + "A %(zfs_fs)s ZFS fájlrendszeren\n" + "%(valid_name)s névvel\n" + "pillanatkép készült.\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + "A Pillanatkép Most kettő paramétert igényel :\n" + "- A könyvtár elérési útvonala, amelyről a pillanatkép készül.\n" + "- A könyvtárnak megfelelő ZFS fájlrendszer." + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Pillanatkép Most futtatásához adminisztratív jogosultságok szükségesek. Ön nem rendelkezik a szükséges adminisztratív jogosultságokkal.\n" + "\n" + "Lépjen kapcsolatba a rendszer adminisztrátorával. " diff --git a/po/id.gmo b/po/id.gmo new file mode 100644 index 0000000000000000000000000000000000000000..0205d6593cd62bc8ad35e38979be5f92a9708e84 GIT binary patch literal 11784 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 +# +msgid "" +msgstr "" + "Project-Id-Version: time-slider HEAD\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2009-12-07 22:37+0900\n" + "PO-Revision-Date: 2010-01-20 20:31+0100\n" + "Last-Translator: Kurniawan Wahyu Illahi, martin\n" + "Language-Team: Indonesian language team \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr " " + "Mengkonfigurasi sistem otomatis untuk mengambil snapshot dari data anda" + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d snapshots akan dihapus" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Hasil" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "D_eselect All" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Menghapus Snapshots" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Menghapus Snapshots" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Menghapus snapshots..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Keluar ketika selesai " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "File Sistem:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Nama:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Tekan Delete untuk melanjutkan." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Memindai snapshots..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Pilih ZFS snapshots untuk menghapus dari daftar dibawah ini." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Enable Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "File Systems To Back Up" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Pilihan Lanjutan" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "C_ustom:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "Sedang mengkonfigurasi Time Slider..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Untuk pengguna lanjutan" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Direkomendasikan untuk sebagian besar pengguna" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Mengurangi ruang penyimpanan backup bila melebihi kapasitas :" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Pengendali Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "Time Slider membackup data secara teratur berdasarkan waktu Snapshot ZFS" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Semua" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Menghapus Snapshots..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "dari kapasitas file sistem" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Mengambil snapshot sekarang" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Mengambil snapshot dari " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "dengan nama :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Versi Saat Ini" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Versi Lama" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Klik dua kali untuk membuka file" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Buka file dengan versi saat ini" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Pemindaian untuk versi lama" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Time Slider File Version Explorer" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "bandingkan" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "nama file" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "modifikasi terakhir : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "nama :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "ukuran : " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "Time Slider: Peringatan Tempat Penyimpanan Hampir Penuh" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Darurat: '%s' penuh!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "file sistem: '%s', melebihi %s%% penuh." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + "File sistem: '%s', is over %s%% full.\n" + "Ukuran darurat, Time Slider akan menghancurkan semua backup.\n" + "Untuk memperbaiki masalah ini, hapus semua file yang tidak diperlukan di '%s', atau tambah ukuran disk." + "" + "(lihat dokumentasi ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Darurat: '%s' hampir penuh!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "File Sistem: '%s', melebihi %s%% dari kapasitas total." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + "File Sistem: '%s', melebihi %s%% dari kapasitas total. Sebagai penanganan darurat, Time Slider telah menghapus sebagian besar atau semua backup untuk mencegah tempat penyimpanan menjadi penuh. Untuk menghindari hal ini terjadi lagi, hapus file-file yang tidak perlu pada '%s', " + "atau tambahkan tempat penyimpanan(lihat dokumentasi ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Mendesak: '%s' hampir penuh!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + "File sistem: '%s', melebihi %s%% kapasitas total. Penanganannya, Time Slider akan menghancurkan beberapa backup, dan akan menghancurkan lainnya, " + "bahkan semuanya, selama kapasitas semakin menurun.\n" + "Untuk menghindari keadaan ini terjadi lagi, hapus file-file yang tidak perlu pada '%s', " + "atau tambahkan tempat penyimpanan(lihat dokumentasi ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Peringatan: '%s' mulai penuh" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + "'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " + "destroyed some recent backups, and will destroy more as capacity continues " + "to diminish.\n" + "To prevent this from happening again, delete any unnecessary files on '%s', " + "or add disk space (see ZFS documentation).\n" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "Rincian..." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Legacy" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Mount Point" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Nama File Sistem" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Nama Snapshot" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Waktu Pembuatan" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Semua" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Bulanan" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Mingguan" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Harian" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Tiap Jam" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Tiap 1/4 jam" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Snapshot tidak dapat dihapus" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + "%s mempunyai satu atau beberapa clone yang tergantung dan tidak akan dihapus. Untuk menghapus snapshot ini, langkah pertama hapus seluruh datasets dan clones snapshots dari snapshot ini." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 snapshot akan di hapus" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Beberapa snapshot tidak dapat dibaca" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Beberapa snapshot tidak dapat dihapus" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Hak akses tidak mencukupi" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Menghapus snapshot membutuhkan hak akses administrasi agar dapat berjalan. Anda belum terdaftar sebagai user yang mempunyai hak akses administrasi.\n" + "\n" + "Konsultasi dengan sistem administrator anda. " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "Petunjuk" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + "Installing the optional meld package will enhance the file comparison " + "visualization" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Kesalahan argumen bertambah" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + "Versi explorer membutuhkan 2 argumen :\n" + "- Path dari direktori root snapshot.\n" + "- Nama file untuk pencarian" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Pilih" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Kesalahan dependensi layanan pengendali Snapshot" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + "Layanan pengendali snapshot berada dalam status offline karena permasalah ketergantungan. Beberapa masalah ketergantungan yang ditemukan: \n" + "\n" + "%s\n" + "\n" + "Jalankan \"svcs -xv\" dari terminal untuk informasi lebih lanjut tentang masalah ketergantungan." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Layanan pengendali Snapshot error" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "Layanan pengendali snapshot mengalami masalah dan akan di nonaktifkan sampai permasalahan di betulkan.\n" + "\n" + "Lihat halaman manual svcs(1) untuk informasi lebih lanjut." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "Layanan pengendali snapshot tidak terlihat terinstall pada sistem anda.\n" + "\n" + "Lihat halaman manual svcs(1) untuk informasi lebih lanjut" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Layanan Pengendali Snapshot membutuhkan hak akses administrasi agar dapat berjalan. Anda belum terdaftar sebagai user yang mempunyai hak akses administrasi.\n" + "\n" + "Konsultasi dengan sistem administrator anda. " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Invalid karakter pada nama snapshot" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Error" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + "Karakter yang diperbolehkan untuk nama snapshot adalah :\n" + "[a-z][A-Z][0-9][-_.:\n" + "Semua karakter yang tidak valid akan di hapus\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Terjadi error ketika membuat snapshot" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Sukses membuat snapshot" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Sukses" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + "Snapshot dari file sistem zfs %(zfs_fs)s\n" + "nama %(valid_name)s\n" + "telah dibuat.\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + "Snapshot Sekarang membutuhkan 2 argumen :\n" + "- Path dari direktori yang akan di snapshot.\n" + "- File sistem zfs yang sesuai dengan direktori ini." + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Snapshot sekarang membutuhkan hak akses administrasi untuk berjalan. Anda belum terdaftar sebagai user yang mempunyai hak akses administrasi.\n" + "\n" + "Konsultasi dengan sistem administrator anda " diff --git a/po/it.gmo b/po/it.gmo new file mode 100644 index 0000000000000000000000000000000000000000..0241b627abb2e24e5b22acfa8865736dd4605496 GIT binary patch literal 10397 zcmd6sZHyh)S;tSC(wCdMp=rZg({l2%*lv8UcO8?&H*u1Uy^h`5vAer=l%!^LcIMo> zC!RZVGcRkeX+)4JKou$ysA`H(fmT9PRa+@iiQogO6d@3@R1gwhr~pNveu;n&@TDJs z-*e{7+}T~baRQY(x^w?`X3m`Ryg$!*?myoCt}iH#3!HayUVEofzXE>dC-~yH@*br= z3T}ZfgI@$c0?xlzsUCP5l%Y?9eAGq0ehM6cKMTGHehmB~cnA0=;0M5K;92k=z@G%~ zd!JIbfRBJ1;Nu{V)YrhH;49!B@IM0nHz@1AA0p)a+ritxdq7#|0Z`~Z4gNg%Joq!< z=RkSytKfd{YoNUUcLDzed_UL!1_%irq@Gfr3 zd*11%ChvUtdPZBKN-nh0gDT8oc$kn$82@hqyi*@Fe)~JCyn~`1jm@8KHaz z%s!~pKY?$6qW^0M<(r`8Wk^>a{xGzF8kG5-02jdvpxD#P-~{|OD0JQflfvI3_%84y z$Rl+I6#1=#qW9-PRICb6^z$Mp?|mK=J$wO_@!tR;S$!LP68irF6ngeB=oRoFC~|lm z6#CwQ@Kw19}!Dqo&!u1}MDf4^`ybRt8%DCSH zPlMk8#eRMea1WD-yfi5H4}c<%$3W57MNstg>!1d|4)Rgg_!7Rq5B9--17+Nu5G(6F z4q_7OQ=stoOQ7(TgRrcA8IK~f+D{+K#~7{g2K=HP}<$#ZJ_wgAyA$# zgD1c-DD!+1`~~oPpy=rqn0X4^1ByOA4Soeoz&`_TLkZ#se+`O0{++MyfFEFyN5Suc z^Kz|~+6O)c&Vd`C@b!66==chV3e>CMGwAzo!1sV3hv+YH{}9Z6i0f78PSCtM>w_o;2@-V+w&KEVKP1@$@L@h#erLRKk*lQiI2!3 zHhF?mY)Jf2j#HegoS3}#k-Na|A^x${+?b1Xk(z0-k(GtM?2<&UTOHfPmNp)!xk7K4 zLT9PfS*|BpF3+Ymjcr=$DB*sgO&ZrPrdip2S+8bcB3*8{qFv^I9=W_In`ee4V^f;K zLV(OQBgZcg>rtLfwofro3+=GoL@yjXtWRcDo@}Xwd3`2R3r7ylFC3gdq8?d)^jMYW zLi`1r7cNU5Ik^6)++Iq}dSdmen^?V)xY*{+lM{~nD_ccrCxx)|go!Tc^V8<>*@Usp zY(;Z-rJ77kzU40$XQXB$eRWj8e_{(iqn_KxjkQs+zv!iAVq

8rENvD{hMP*R4%; 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 +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2009-02-16 18:42+0100\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "" +"Configurare il sistema per fare delle istantanee automatiche dei propri dati" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Time Slider" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "Verranno eliminate %d istantanee." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Riepilogo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "D_eseleziona tutto" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Elimina istantanee" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Eliminazione delle istantanee" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Eliminazione delle istantanee in corso…" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Chiudi al termine dell'operazione. " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "File system:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Nome:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Premere Elimina per continuare." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Scansione delle istantanee in corso…" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "" +"Selezionare le istantanee ZFS da eliminare dall’elenco di seguito riportato." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Abilita Time Slider " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "File system di cui eseguire il backup" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Opzioni avanzate" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "Personali_zzato:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Per utenti avanzati" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Per tutti gli utenti" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Ridurre i backup quando lo spazio di memorizzazione utilizzato supera:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Gestione Time Slider" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" +"Time Slider consente di eseguire regolarmente backup dei dati mediante " +"l'acquisizione di istantanee ZFS a intervalli regolari" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Tutto" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Elimina istantanee…" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "della capacità del file system" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Crea un’istantanea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Crea un’istantanea di " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "con il nome :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Versione corrente" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Versioni precedenti" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Fare doppio clic per aprire il file" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Apri la versione corrente del file" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Ricerca delle versioni precedenti in corso..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Explorer versione del file Time Slider" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "confronta" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "nome file" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "ultima modifica : " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "nome :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "dimensioni : " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "Gestione Time Slider" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Attenzione: '%s' è pieno." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"Spazio del file system '%s' occupato oltre il %s%%.\n" +"Come misura di emergenza, Time Slider ha eliminato tutti i backup eseguiti.\n" +"Per risolvere il problema, eliminare i file non necessari in '%s' oppure " +"aggiungere spazio nel disco (vedere la documentazione di ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Attenzione: '%s' è quasi pieno." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"La capacità totale del file system '%s' è stata superata del %s%%. Come " +"misura di emergenza, Time Slider ha eliminato tutti o la maggior parte dei " +"backup per impedire che lo spazio su disco venga esaurito. Per evitare il " +"ripetersi del problema, eliminare eventuali file non necessari in '%s' " +"oppure aggiungere spazio su disco (vedere la documentazione di ZFS).\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Avviso: '%s' è quasi pieno." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"La capacità totale del file system '%s' è stata superata del %s%%. Come " +"rimedio, Time Slider ha eliminato alcuni backup e ne eliminerà altri o tutti " +"man mano che la capacità diminuisce.\n" +"Per evitare il ripetersi del problema, eliminare eventuali file non " +"necessari in '%s' oppure aggiungere spazio sul disco (vedere la " +"documentazione di ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Avvertenza: lo spazio su '%s' sta per esaurirsi" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"'%s' ha superato la relativa capacità complessiva del %s%%. Per risolvere il " +"problema, Time Slider ha eliminato alcuni backup recenti e ne eliminerà " +"altri man mano che la capacità diminuisce.\n" +"Per evitare il ripetersi del problema, eliminare eventuali file non " +"necessari in '%s' oppure aggiungere spazio su disco (vedere la " +"documentazione di ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Legacy" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Punto di attivazione" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Nome file system" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Nome istantanea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Tempo di creazione" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Tutto" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Mensile" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Settimanale" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Ogni giorno" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Ogni ora" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Ogni quarto d’ora" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Impossibile eliminare l’istantanea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s possiede uno o più cloni dipendenti e non verrà eliminata. Per eliminare " +"questa istantanea, eliminare prima tutti i set di dati e le istantanee " +"clonate da questa istantanea." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 istantanea verrà eliminata." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Impossibile leggere alcune istantanee" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Impossibile eliminare alcune istantanee " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Privilegi insufficienti" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Per l'esecuzione dell’eliminazione delle istantanee sono necessari privilegi " +"amministrativi. Non si dispone di privilegi amministrativi necessari.\n" +"\n" +"Rivolgersi all'amministratore di sistema. " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Calcolo degli argomenti non valido." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"L’explorer della versione richiede 2 argomenti :\n" +"- La path della directory radice dell’istantanea .\n" +"- Il nome file da ricercare." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Seleziona" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Errore di dipendenza del servizio di gestione delle istantanee" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"Il servizio di gestione delle istantanee è stato disconnesso a causa di un " +"problema di dipendenza. Sono stati riscontrati i seguenti problemi di " +"dipendenza:\n" +"\n" +"%s\n" +"\n" +"Eseguire \"svcs -xv\" da un prompt di comando per ottenere maggiori " +"informazioni su questi problemi di dipendenza." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Errore del servizio di gestione delle istantanee" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"Il servizio di gestione delle istantanee ha riscontrato un problema ed è " +"stato disabilitato fino alla risoluzione del problema.\n" +"\n" +"Per ulteriori informazioni consultare la pagina man svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"Il servizio di gestione delle istantanee non sembra essere installato nel " +"sistema corrente.\n" +"\n" +"Per ulteriori informazioni consultare la pagina man svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Per l'esecuzione del servizio di gestione delle istantanee sono necessari " +"privilegi amministrativi. Non si dispone di privilegi amministrativi " +"sufficienti.\n" +"\n" +"Rivolgersi all'amministratore di sistema. " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Caratteri invalidi nel nome dell’istantanea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Errore" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"I caratteri ammessi per i nomi delle istantanee sono :\n" +"[a-z][A-Z][0-9][-_.:\n" +"Tutti i caratteri invalidi saranno rimossi\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Errore durante la creazione dell’istantanea" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Istantanea creata con successo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Operazione riuscita" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"Un’istantanea del file system zfs %(zfs_fs)s\n" +"denominata %(valid_name)s\n" +"è stata creata.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"Crea istantanea richiede 2 argomenti :\n" +"- La path della directory di cui deve essere creata l’istantanea.\n" +"- Il file system zfs corrispondente a questa directory." + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Per l’esecuzione del comando Crea istantanea sono necessari privilegi " +"amministrativi. Non si dispone dei privilegi amministrativi necessari.\n" +"\n" +"Rivolgersi all'amministratore di sistema. " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "" +#~ "A snapshot of zfs filesystem %s\n" +#~ "named %s\n" +#~ "has been created.\n" +#~ msgstr "" +#~ "Un’istantanea del file system zfs %s\n" +#~ "denominata %s\n" +#~ "è stata creata.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "" +#~ "Elimina i backup meno recenti quando lo spazio di memorizzazione " +#~ "utilizzato supera:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "di capacità del pool" diff --git a/po/ja.gmo b/po/ja.gmo new file mode 100644 index 0000000000000000000000000000000000000000..df91b1eb1ad3f69bd9e5b996a1320bca43e2a9b7 GIT binary patch literal 11878 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 literal 0 HcmV?d00001 diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 0000000..bdc44c7 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,944 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2008-10-01 21:35+0900\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "自動的にデータのスナップショットを作成するようにシステムを構成します" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "タイムスライダ" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d 個のスナップショットが削除されます。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "要約" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "すべてを選択解除(_E)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "スナップショットの削除" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "スナップショットを削除しています" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "スナップショットを削除しています..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "完了したら終了します。 " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "ファイルシステム: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "名前: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "続行するには「削除」を押します。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "スナップショットをスキャンしています..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "下の一覧から削除する ZFS スナップショットを選択します。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "タイムスライダを有効にする" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "バックアップするファイルシステム" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "詳細オプション" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "カスタム(_U): " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "上級ユーザー向け" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "ほとんどのユーザーに推奨" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "ストレージスペースの使用量が超過したらバックアップを削減する:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "タイムスライダマネージャー" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" +"タイムスライダは、時刻指定の ZFS スナップショットを作成することによって定期的" +"にデータをバックアップします" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "すべて(_A)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "スナップショットの削除(_D)..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "ファイルシステム容量" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "今すぐスナップショットを取得する" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "スナップショットを取得する対象 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "名前: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "現在のバージョン" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "旧バージョン" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "ダブルクリックしてファイルを開く" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "ファイルの現在のバージョンを開く" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "旧バージョンをスキャンしています" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "タイムスライダファイルバージョンエクスプローラ" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "比較" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "ファイル名" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "最終更新日付: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "名前: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "サイズ: " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "タイムスライダマネージャー" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "緊急: '%s' がいっぱいです。" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"ファイルシステム: '%s' が総容量の %s%% を超えました。\n" +"緊急の手段として、タイムスライダはすべてのバックアップを破棄しました。\n" +"この問題を解決するために、'%s' 上の不必要なファイルをすべて削除するか、または" +"ディスク容量を追加してください (ZFS のマニュアルを参照)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "緊急: '%s' がほぼいっぱいです。" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"ファイルシステム: '%s' が総容量の %s%% を超えました。緊急の手段として、ディス" +"クがいっぱいになることを避けるために、タイムスライダはほとんどまたはすべての" +"バックアップを破棄しました。この問題がふたたび発生しないようにするために、'%" +"s' 上の不必要なファイルをすべて削除するか、またはディスク容量を追加してくださ" +"い (ZFS のマニュアルを参照)。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "緊急: '%s' がほぼいっぱいです。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"ファイルシステム: '%s' が総容量の %s%% を超えました。是正手段として、タイムス" +"ライダはすでに一部のバックアップを破棄しており、容量が減り続ければさらに多く" +"のバックアップ、最終的にはすべてのバックアップを破棄します。\n" +"この問題がふたたび発生しないようにするために、'%s' 上の不必要なファイルをすべ" +"て削除するか、またはディスク容量を追加してください (ZFS のマニュアルを参照)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "警告: '%s' がいっぱいになりつつあります" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"'%s' が総容量の %s%% を超えました。この問題を解決するために、タイムスライダは" +"すでに一部の最新のバックアップを破棄しており、容量が減り続ければさらに多くの" +"バックアップを破棄します。\n" +"この問題がふたたび発生しないようにするために、'%s' 上の不必要なファイルをすべ" +"て削除するか、またはディスク容量を追加してください (ZFS のマニュアルを参照)。" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "旧バージョン" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "マウントポイント" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "ファイルシステム名" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "スナップショット名" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "作成時間" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "すべて" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "毎月" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "毎週" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "毎日" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "毎時" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "15 分ごと" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "スナップショットを削除できません" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s には 1 つ以上の複製が依存しているため、削除されません。このスナップショッ" +"トを削除するには、まず、このスナップショットから複製されたすべてのデータセッ" +"トとスナップショットを削除してください。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 つのスナップショットが削除されます。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "いくつかのスナップショットを読み取れませんでした" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "いくつかのスナップショットを削除できませんでした" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "権限が不足しています" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"スナップショットの削除を実行するには管理権限が必要です。必要な管理権限が割り" +"当てられていません。\n" +"\n" +"システム管理者に問い合わせてください" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "引数の数が無効です。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"バージョンエクスプローラには次の 2 つの引数が必要です:\n" +"- ルートスナップショットディレクトリのパス。\n" +"- 調査するファイル名。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "選択" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "スナップショットマネージャーサービスの依存関係エラー" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"依存関係の問題が発生したために、スナップショットマネージャーサービスがオフラ" +"インになりました。次の依存関係の問題が検出されました:\n" +"\n" +"%s\n" +"\n" +"これらの依存関係の問題の詳細を表示するには、コマンドプロンプトから \"svcs -xv" +"\" を実行してください。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "スナップショットマネージャーサービスのエラー" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"スナップショットマネージャーサービスが問題を検出し、この問題が解決されるまで" +"無効になっています。\n" +"\n" +"詳細は、svcs(1) のマニュアルページを参照してください。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"このシステムにはスナップショットマネージャーサービスがインストールされていな" +"いようです。\n" +"\n" +"詳細は、svcs(1) のマニュアルページを参照してください。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"スナップショットマネージャーサービスを実行するには管理権限が必要です。必要な" +"管理権限が割り当てられていません。\n" +"\n" +"システム管理者に問い合わせてください" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "スナップショット名の文字が無効です" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "エラー" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"スナップショット名に許可されている文字は次のとおりです:\n" +"[a-z][A-Z][0-9][-_.:\n" +"無効な文字はすべて削除されます。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "スナップショットの作成中にエラーが発生しました" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "スナップショットが正常に作成されました" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "成功" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"zfs ファイルシステム %(zfs_fs)s のスナップショット\n" +"(名前は %(valid_name)s)\n" +"が作成されました。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"「今すぐスナップショット」には次の 2 つの引数が必要です:\n" +"- スナップショットを取得するディレクトリのパス。\n" +"- このディレクトリに対応する zfs ファイルシステム。" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"「今すぐスナップショット」を実行するには管理権限が必要です。必要な管理権限が" +"割り当てられていません。\n" +"\n" +"システム管理者に問い合わせてください" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#~ msgid "" +#~ "A snapshot of zfs filesystem %s\n" +#~ "named %s\n" +#~ "has been created.\n" +#~ msgstr "" +#~ "zfs ファイルシステム %s のスナップショット\n" +#~ "(名前は %s)\n" +#~ "が作成されました。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "" +#~ "ストレージスペースの使用量が超過したらもっとも古いバックアップを破棄する:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN TECH REVIEWED +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "プール容量" diff --git a/po/ko.gmo b/po/ko.gmo new file mode 100644 index 0000000000000000000000000000000000000000..fed42da3edf8af4e6e1f740ea58f0795c8dfa553 GIT binary patch literal 10521 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 +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2008-10-01 21:35+0900\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "사용자 데이터의 자동 스냅샷을 만들도록 시스템 구성" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "시간 슬라이더" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d개의 스냅샷이 삭제됩니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "요약" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "모두 선택 해제(_E)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "스냅샷 삭제" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "스냅샷 삭제 중" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "스냅샷 삭제 중..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "완료 후 종료합니다. " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "파일 시스템:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "이름:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "계속하려면 삭제를 누르십시오." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "스냅샷 검색 중..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "다음 목록에서 삭제할 ZFS 스냅샷을 선택하십시오." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "시간 슬라이더 활성화" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "백업할 파일 시스템" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "고급 옵션" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "사용자 정의(_U):" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "고급 사용자용" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "대부분의 사용자에게 권장" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "저장소 공간 사용량이 초과되는 경우 백업 줄이기:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "시간 슬라이더 관리자" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" +"시간 슬라이더는 시간이 지정된 ZFS 스냅샷을 가져와 데이터를 정기적으로 백업합" +"니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "모두(_A)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "스냅샷 삭제(_D)..." + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "- 파일 시스템 용량" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "지금 스냅샷 실행" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "다음 항목의 스냅샷 실행 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "이름:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "현재 버전" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "이전 버전" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "파일을 열려면 두 번 누릅니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "파일의 현재 버전을 엽니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "이전 버전 검색 중" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "시간 슬라이더 파일 버전 탐색기" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "비교" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "파일 이름" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "마지막 수정: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "이름:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "크기: " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "시간 슬라이더 관리자" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "긴급: '%s'이(가) 꽉 찼습니다!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"파일 시스템 '%s'이(가) %s%% 이상으로 꽉 찼습니다.\n" +"긴급 조치로 시간 슬라이더가 모든 백업을 삭제했습니다\n" +"이 문제를 해결하려면 '%s'에서 불필요한 파일을 삭제하거나 디스크 공간을 추가하" +"십시오(ZFS 설명서 참조)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "긴급: '%s'이(가) 거의 찼습니다!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"파일 시스템 '%s'이(가) 총 용량의 %s%%을(를) 초과했습니다. 긴급 조치로 시간 슬" +"라이더가 백업을 모두 삭제하거나 대부분 삭제하여 디스크가 꽉 차는 문제를 방지" +"했습니다. 이러한 문제가 다시 발생하지 않도록 '%s'에서 불필요한 파일을 삭제하" +"거나 디스크 공간을 추가합니다(ZFS 설명서 참조).\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "중요: '%s'이(가) 거의 찼습니다!" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"파일 시스템 '%s'이(가) 총 용량의 %s%%을(를) 초과했습니다. 해결 조치로시간 슬" +"라이더가 일부 백업을 삭제했으며, 용량이 계속 줄어들 때 좀 더 삭제할 예정이며 " +"ê²°êµ­ 모두 삭제합니다.\n" +"이러한 문제가 다시 발생하지 않도록 '%s'에서 불필요한 파일을 삭제하거나 디스" +"크 공간을 추가합니다(ZFS 설명서 참조)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "경고: '%s'이(가) 꽉 찼습니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"'%s'이(가) 총 용량의 %s%%을(를) 초과했습니다. 이 문제를 해결하기 위해 시간 슬" +"라이더가 최근의 일부 백업을 삭제했으며 용량이 계속 줄어들 때 좀 더 삭제할 예" +"정입니다.\n" +"이러한 문제가 다시 발생하지 않도록 '%s'에서 불필요한 파일을 삭제하거나 디스" +"크 공간을 추가합니다(ZFS 설명서 참조)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "레거시" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "마운트 지점" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "파일 시스템 이름" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "스냅샷 이름" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "생성 시간" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "모두" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "매달" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "매주" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "매일" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "매시간" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "매25분" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "스냅샷을 삭제할 수 없습니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s에 하나 이상의 종속 복제가 있어 삭제되지 않습니다. 이 스냅샷을 삭제하려면 " +"먼저 이 스냅샷에서 복제된 모든 데이터 집합과 스냅샷을 삭제하십시오." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1개의 스냅샷이 삭제됩니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "일부 스냅샷을 읽을 수 없습니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "일부 스냅샷을 삭제할 수 없습니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "권한 부족" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"스냅샷 삭제 기능을 실행하려면 관리 권한이 필요합니다. 필요한 관리 권한이 할" +"당되지 않았습니다.\n" +"\n" +"시스템 관리자에게 문의하십시오. " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "잘못된 인수 카운트입니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"버전 탐색기에 다음과 같은 2개의 인수가 필요합니다.\n" +"- 루트 스냅샷 디렉토리의 경로\n" +"- 탐색할 파일 이름" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "선택" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "스냅샷 관리자 서비스 종속성 오류" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"스냅샷 관리자 서비스가 종속성 문제로 인해 오프라인 상태로 설정되었습니다. 다" +"음과 같은 종속성 문제가 발견되었습니다.\n" +"\n" +"%s\n" +"\n" +"이러한 종속성 문제에 대한 자세한 내용을 보려면 명령 프롬프트에서 \"svcs -xv" +"\"를 실행하십시오." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "스냅샷 관리자 서비스 오류" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"스냅샷 관리자 서비스에 문제가 발생하여 문제가 수정될 때까지 비활성화되었습니" +"다.\n" +"\n" +"자세한 내용은 svcs(1) 매뉴얼 페이지를 참조하십시오." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"스냅샷 관리자 서비스가 이 시스템에 설치된 것으로 나타나지 않습니다.\n" +"\n" +"자세한 내용은 svcs(1) 매뉴얼 페이지를 참조하십시오." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"스냅샷 관리자 서비스를 실행하려면 관리 권한이 필요합니다. 필요한관리 권한이 " +"지정되지 않았습니다.\n" +"\n" +"시스템 관리자에게 문의하십시오. " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "스냅샷 이름에 잘못된 문자가 있습니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "오류" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"스냅샷 이름에 사용할 수 있는 문자는 다음과 같습니다.\n" +"[a-z][A-Z][0-9][-_.:\n" +"잘못된 문자는 모두 제거됩니다.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "스냅샷 생성 중 오류가 발생했습니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "스냅샷이 성공적으로 생성되었습니다." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "성공" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"zfs 파일 시스템 %(zfs_fs)s의 스냅샷\n" +"%(valid_name)s이(가)\n" +"생성되었습니다.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"지금 스냅샷 기능에는 다음과 같은 2개의 인수가 필요합니다.\n" +"- 스냅샷될 디렉토리의 경로\n" +"- 이 디렉토리에 해당하는 zfs 파일 시스템" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"지금 스냅샷 기능을 실행하려면 관리 권한이 필요합니다. 필요한 관리 권한이 할당" +"되지 않았습니다.\n" +"\n" +"시스템 관리자에게 문의하십시오. " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "" +#~ "A snapshot of zfs filesystem %s\n" +#~ "named %s\n" +#~ "has been created.\n" +#~ msgstr "" +#~ "zfs 파일 시스템 %s의 스냅샷\n" +#~ "%s이(가)\n" +#~ "생성되었습니다.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "저장소 공간 사용량이 초과되는 경우 가장 오래된 백업 삭제:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "풀 용량" diff --git a/po/nl.gmo b/po/nl.gmo new file mode 100644 index 0000000000000000000000000000000000000000..5d13d52684a48ff16135ee361294f87e68ed55b6 GIT binary patch literal 11986 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 +# +msgid "" +msgstr "" + "Project-Id-Version: time-slider HEAD\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2009-02-10 22:37+0900\n" + "PO-Revision-Date: 2010-01-20 22:14+0900\n" + "Last-Translator: Marlen K. Waaijer\n" + "Language-Team: Dutch (Nederlands) \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "Configureer het systeem om automatische momentopnamen van je gegevens te nemen" + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Tijd glijder" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d momentopnamen zullen worden verwijdert" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Samenvatting" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "D_eselecteer alle" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Verwijder momentopnamen" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Momentopnames worden verwijdert" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Momentopnamen worden verwijdert..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Sluit af als je klaar bent. " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Bestand systeem:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Naam:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Druk delete om door te gaan." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Momentopnamen aan het scanderen..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Kies de te verwijderen ZFS momentopnamen van de lijst beneden. " + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Schakel tijd slijder in" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Bestand systemen voor backup" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Geavanceerde opties" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "C_ustom:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "Configuring Time Slider..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Voor geavanceerde gebruikers" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Aanbevolen voor de meeste gebruikers" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Verminder back-ups als opslagruimte gebruik meer bedraagt dan:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Tijd glijder beheerder" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "Tijd glijder maakt reservekopieën van gegevens door het nemen van een regelmatige ZFS momentopnamen" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Alle" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Verwijder momentopnames..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "van bestand systeem capaciteit" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Neem nu een momentopname" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Neem een momentopname van " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "met de naam :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 MO" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Huidige versie" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Oudere versies" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Dubbelklik om het bestand te openen" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Open de huidige versie van het bestand" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Zoeken naar oudere versies" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Tijd slijder bestand versie onverzoeker" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "vergelijk" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "bestand naam" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-sluiten" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "laatst bewerkt : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "name :" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "grootte : " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "Time Slider: Low Space Warning" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Alarm: '% s' is vol!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "The file system: '%s', is over %s%% full." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + "Het bestandssysteem: '%s' is meer dan% s%% vol. \n" + "Als een noodmaatregel, Time Slider heeft alle back-ups vernietigd. \n" + "Om dit probleem te verhelpen, verwijdert u overbodige bestanden op '% s' of voeg schijfruimte" + "(zie documentatie ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Noodgeval: '% s' is bijna vol!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "The file system: '%s', exceeded %s%% of its total capacity" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + "The file system: '%s', exceeded %s%% of its total capacity. As an emerency " + "measure, Time Slider has has destroyed most or all of its backups to prevent " + "the disk becoming full. To prevent this from happening again, delete any " + "unnecessary files on '%s', or add disk space (see ZFS documentation)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Urgent: '% s' is bijna vol!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + "Het bestandssysteem: '%s', heeft %s%% van de totale capaciteit overschreden. Zoals een corrigerende" + "maatregel, Time Slider heeft een aantal back-ups vernietigd, en zal daarmee doorgaan , " + "totdat uiteindelijk alle back-ups vernietigd zijn, zolang de capaciteit blijft afnemen.\n" + "Om te voorkomen dat dit opnieuw gebeurt, verwijder alle overbodige bestanden op '%s'," + "of voeg schrijfruimte toe (zie ZFS documentatie)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Waarschuwing: '%s' wordt voller en voller" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + "'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " + "destroyed some recent backups, and will destroy more as capacity continues " + "to diminish.\n" + "To prevent this from happening again, delete any unnecessary files on '%s', " + "or add disk space (see ZFS documentation).\n" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "Details..." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Legaat" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Monteer punt" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Bestand systeem naam" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Momentopname naam" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Aanmaaktijd" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Alle" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Maandelijks" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Wekelijks" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Dagelijks" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Ieder uur" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Ieder kwartier" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Momentopname kan niet gewist worden" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + "% s heeft een of meer afhankelijke klonen en zal niet worden verwijderd. Om deze momentopname te verwijderen," + "moeten eerst alle gekloonde datasets en momentopnames van deze momentopname verwijdert worden." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 momentopname zal niet verwijdert worden" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Sommige momentopnames konden niet gelezen worden" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Sommige momentopnames konden niet verwijdert worden" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Onvoldoende Privileges" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Momentopname verwijdering vereist administratieve privileges om te lopen. Je hebt niet " + "de nodige administratieve privileges.\n" + "\n" + "Raadpleeg je systeembeheerder " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "Hint" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + "Installing the optional meld package will enhance the file comparison " + "visualization" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Ongeldige argumenten aantal." + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + "Versie ontdekker vereist 2 argumenten:\n" + "- Het pad van de root locatie voor de momentopname.\n" + "- De bestandsnaam te die gezocht wordt." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Selecteer" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Momentopname manager service afhankelijkheid fout" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + "De momentopname manager dienst is uitgeschakeld wegens een afhankelijkheids" + "probleem. De volgende afhankelijkheidsproblemen waren gevonden:\n" + "\n" + "%s\n" + "\n" + "Typ de opdrachtregel \" svcs -xv\" in een commandovenster voor meer informatie over deze " + "afhankelijkheidsproblemen." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Momentopname manager service fout" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "De momentopname manager dienst heeft een probleem ondergaan en is uitgeschakeld " + "totdat het probleem is opgelost.\n" + "\n" + "Zie de Svcs (1) man pagina voor meer informatie." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "De momentopname manager dienst blijkt niet geïnstalleerd te zijn op dit" + "systeem.\n" + "\n" + "Zie de svcs(1) man pagina voor meer informatie." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "De momentopname manager service vereist administratieve privileges om te lopen. U" + "heeft niet de vereiste administratieve privileges. \n" + "\n" + "Raadpleeg uw systeembeheerder " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Ongeldige tekens in momentopname naam" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Fout" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + "Geldige tekens voor momentopname namen zijn:\n" + "[a-z] [A-Z] [0-9 ][-_.: \n" + "Alle ongeldig tekens worden verwijderd\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Fout opgetreden tijdens het maken van de momentopname" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Momentopname gemaakt" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Succes" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + "Een momentopname van zfs bestandssystem %(zfs_fs)s\n" + "genaamd %(valid_name)s\n" + "was gemaakt.\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + "Momentopname vereist nu 2 argumenten :\n" + "- Het pad van de locatie waarvan een momentopname van gemaakt wordt.\n" + "- De zfs bestandssysteem dat met deze locatie correspondeert." + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Momentopname vereist nu administratieve privileges om te lopen. Je hebt niet " + "de nodige administratieve privileges. \n" + "\n" + "Raadpleeg uw systeembeheerder " diff --git a/po/pl.gmo b/po/pl.gmo new file mode 100644 index 0000000000000000000000000000000000000000..7f86baee8eb30aae6b8f81a6b402101ceb1a900e GIT binary patch literal 11738 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 E3;aivFaQ7m literal 0 HcmV?d00001 diff --git a/po/pl.po b/po/pl.po new file mode 100644 index 0000000..0989f9d --- /dev/null +++ b/po/pl.po @@ -0,0 +1,538 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" + "Project-Id-Version: time-slider HEAD\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2009-12-07 22:37+0900\n" + "PO-Revision-Date: 2010-01-20 22:37+0900\n" + "Last-Translator: bartus11, geek85\n" + "Language-Team: Polish language team \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "Skonfiguruj system do automatycznego wykonywania migawek twoich danych " + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d migawki zostaną usunięte" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Podsumowanie" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "O_dznacz wszystkie" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Usuwam migawki" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Usuwanie migawki" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Usuwanie migawek..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Wyjdź kiedy skończysz. " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "System plików:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Nazwa:" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Naciśnij Delete żeby kontynuować. " + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Wyszukuje migawki..." + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Wybierz migawki ZFS do usunięcia z listy poniżej." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Włącz Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "System plików do Back Up" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Zaawansowane opcje" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "C_ustom:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "Configuring Time Slider..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Dla zaawansowanych użytkowników" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Rekomendowane dla większości użytkowników" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Zmniejsz liczbę backupów kiedy przestrzeń przechowywania przekroczy:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Menadżer Time Slider" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "Time Slider regularnie robi kopię zapasową danych poprzez migawkę ZFS" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Wszystkie" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Usuwanie Migawki..." + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "możliwości systemu plików" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Zrób migawkę teraz" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Zrób migawkę " + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "z nazwą:" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr " Wersja bieżąca" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Starsza Wersja" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Kliknij dwa razy, żeby otworzyć plik" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Otwórz bieżącą wersję pliku" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Wyszukuje starsze wersje" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Time Slider File Version Explorer" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "porównaj" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "nazwa pliku" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-zamknij" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "ostatnia modyfikacja : " + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "nazwa:" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "rozmiar: " + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "Time Slider: Low Space Warning" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Uwaga: '%s' jest zapełniony" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "The file system: '%s', is over %s%% full." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + "System plików: '%s', jest zapełniony w %s%%.\n" + "Jako środek zaradczy, Time Slider usunął wszystkie migawki tego systemu plików.\n" + "Aby rozwiązać ten problem, usuń niepotrzebne pliki z '%s' lub dodaj przestrzeń dyskową" + "(zobacz dokumentację ZFS)" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Uwaga: '%s' jest prawie zapełniony" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "The file system: '%s', exceeded %s%% of its total capacity" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + "The file system: '%s', exceeded %s%% of its total capacity. As an emerency " + "measure, Time Slider has has destroyed most or all of its backups to prevent " + "the disk becoming full. To prevent this from happening again, delete any " + "unnecessary files on '%s', or add disk space (see ZFS documentation)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Pilne: '%s' jest prawie zapełniony!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + "System plików: '%s', przekroczył %s%% jego całkowitej pojemności. Jako środek zaradczy Time Slider usunął kilka migawek tego systemu i w końcu usunie wszystkie, jeżeli wolne miejsce będzie nadal ubywać. Aby ta sytuacja się nie powtórzyła, usuń niepotrzebne pliki z '%s' lub dodaj przestrzeń dyskową (zobacz dokumentację ZFS)" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Ostrzeżenie: '%s' staje się pełny" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + "'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " + "destroyed some recent backups, and will destroy more as capacity continues " + "to diminish.\n" + "To prevent this from happening again, delete any unnecessary files on '%s', " + "or add disk space (see ZFS documentation).\n" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "Details..." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Legacy" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Punkt montowania" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Nazwa systemu plików" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Nazwa migawki" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Czas utworzenia" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Wszystkie" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Miesięcznie" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Tygodniowo" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Dziennie" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "Co godzinę" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Co 15 minut" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "Migawka nie może byś usunięta" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + "%s posiada jeden lub więcej zależnych klonów i nie zostanie usunięty. Aby usunąc tą" + "migawkę, najpierw usuń wszystkie datasety i migawki sklonowane z tej migawki." + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 migawka będzie usunięta" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Nie można odczytać niektórych migawek" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Nie można usunąć niektórych migawek" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Niewystarczające uprawnienia" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Usunięcie migawki wymaga uprawnień administratora. Nie masz przypisanych niezbędnych uprawnień administracyjnych.\n" + "\n" + "Skontaktuj się ze swoim administratorem systemu. " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "Hint" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + "Installing the optional meld package will enhance the file comparison " + "visualization" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Niepoprawna liczba argumentów." + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + "Wersja przeglądarki potrzebuje 2 argumenty :\n" + "-Ścieżkę do głównego katalogu migawek.\n" + "Nazwę pliku do zbadania." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Wybierz" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Błąd usługi menadżera migawek" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + "Menadżer migawek został wyłączony z powodu problemu z zależnościami. Następujące problemy z zależnościami zostały wykryte:\n" + "\n" + "%s\n" + "\n" + "Wykonaj \"svcs -xv\" w terminalu aby uzyskać więcej informacji na temat tych problemów." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Błąd menadżera migawek" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "Menadżer migawek napotkał problem i został wyłączony dopóki problem nie zostanie rozwiązany.\n" + "\n" + "Zobacz stronę manuala svcs(1) aby uzyskać więcej informacji." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + "Menadżer migawek nie jest zainstalowany na tym systemie.\n" + "\n" + "Zobacz stronę manuala svcs(1) aby uzyskać więcej informacji." + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Usługa menadżera migawek wymaga uprawnień administratora do działania. Nie masz przypisanych niezbędnych uprawnień administracyjnych.\n" + "\n" + "Skontaktuj się ze swoim administratorem systemu. " + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Niedozwolone znaki w nazwie migawki" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Błąd" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + "Dozwolone znaki w nazwie migawki :\n" + "[a-z][A-Z][0-9][-_.:\n" + "Wszystkie inne znaki zostaną usunięte\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Wystąpił błąd podczas tworzenia migawki" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Migawka została utworzona" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Powodzenie" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + "Utworzono migawkę systemu plików zfs %(zfs_fs)s\n" + "o nazwie %(valid_name)s\n" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + "Migawka Teraz potrzebuje 2 argumenty :\n" + "- Ścieszkę dostępu do katalogu z którego migawka ma zostać utworzona.\n" + "- System plików zfs powiązany z katalogiem." + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + "Migawka Teraz wymaga uprawnień administratora do działania. Nie masz przypisanych niezbędnych uprawnień administracyjnych.\n" + "\n" + "Skontaktuj się ze swoim administratorem systemu. " diff --git a/po/pt_BR.gmo b/po/pt_BR.gmo new file mode 100644 index 0000000000000000000000000000000000000000..4f983313e2cff0ac10e579ab31b0c44f693165c7 GIT binary patch literal 10542 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 +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2009-02-16 18:49+0100\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "Configurar o sistema para realizar instantâneos automáticos dos dados" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "Barra deslizante de tempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d instantâneos serão excluídos." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "Resumo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "Cancelar s_eleção de todos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "Excluir instantâneos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "Excluindo instantâneos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "Excluindo instantâneos..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "Sair ao concluir. " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "Sistema de arquivos:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "Nome:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "Pressione Delete para continuar." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "Explorando instantâneos..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "Selecione na lista abaixo os instantâneos do ZFS que serão excluídos." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "Ativar a barra deslizante de tempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "Sistemas de arquivos dos quais fazer backup" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "Opções avançadas" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "P_ersonalizado:" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "Para usuários avançados" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "Recomendado para a maioria dos usuários" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "Reduzir os backups quando o espaço de armazenamento for excedido:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "Gerenciador da barra deslizante de tempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" +"A barra deslizante de tempo faz backup de dados regularmente realizando " +"instantâneos programados do ZFS" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "_Todos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "_Excluir instantâneos..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "da capacidade do sistema de arquivos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "Efetuar um instantâneo agora" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "Efetuar um instantâneo de " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "com o nome:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "Versão atual" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "Versões mais antigas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "Clicar duas vezes para abrir o arquivo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "Abrir a versão atual do arquivo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "Explorando versões mais antigas" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "Explorador de versões de arquivos da barra deslizante de tempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "comparar" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "nome de arquivo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "última modificação: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "nome :" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "tamanho : " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "Gerenciador da barra deslizante de tempo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "Emergência: '%s' está cheio!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"O sistema de arquivos: '%s' está %s%% acima.\n" +"Como medida de emergência, a barra deslizante de tempo eliminou todos os " +"backups.\n" +"Para corrigir este problema, exclua os arquivos desnecessários de '%s' ou " +"adicione espaço em disco (consulte a documentação do ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "Emergência: '%s' está quase cheio!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"O sistema de arquivos: '%s' excedeu %s%% da capacidade total. Como medida de " +"emergência, a barra deslizante de tempo eliminou a maioria ou todos os " +"backups para evitar que o disco ficasse cheio. Para evitar que isso acorra " +"novamente, exclua os arquivos desnecessários de '%s' ou adicione espaço em " +"disco (consulte a documentação do ZFS).\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "Urgente: '%s' está quase cheio!" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"O sistema de arquivos: '%s' excedeu %s%% da capacidade total. Como medida de " +"reforço, a barra deslizante de tempo eliminou alguns backups e eliminará " +"mais, eventualmente todos, caso a capacidade continue diminuindo.\n" +"Para evitar que isso acorra novamente, exclua os arquivos desnecessários de " +"'%s' ou adicione espaço em disco (consulte a documentação do ZFS)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "Aviso: '%s' está ficando cheio" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"'%s' excedeu %s%% da capacidade total. Para corrigir esse problema, a barra " +"deslizante de tempo eliminou alguns backups recentes e eliminará mais caso a " +"capacidade continue diminuindo.\n" +"Para evitar que isso acorra novamente, exclua os arquivos desnecessários de " +"'%s' ou adicione espaço em disco (consulte a documentação do ZFS)." + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "Legado" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "Ponto de montagem" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "Nome do sistema de arquivos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "Nome do instantâneo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "Hora de criação" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "Todos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "Mensalmente" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "Semanalmente" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "Diariamente" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "De hora em hora" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "Cada 15 minutos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "O instantâneo não pode ser excluído" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s tem um ou mais clones dependentes e não será excluído. Para excluir este " +"instantâneo, exclua primeiro todos os conjuntos de dados e instantâneos " +"clonados deste instantâneo." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 instantâneo será excluído." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "Alguns instantâneos não podem ser lidos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "Alguns instantâneos não podem ser excluídos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "Privilégios insuficientes" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"A exclusão de instantâneos requer privilégios administrativos para ser " +"executada. Os privilégios administrativos necessários não lhe foram " +"atribuídos.\n" +"\n" +"Consulte o administrador de sistema " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "Total de argumentos inválidos." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"O explorador de versão requer 2 argumentos :\n" +"- O caminho do diretório raiz do instantâneo.\n" +"- O nome de arquivo para exploração." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "Selecione" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "Erro de dependência do serviço do gerenciador de instantâneos" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"O serviço do gerenciador de instantâneos foi colocado off-line devido a um " +"problema de dependência. Foram encontrados os seguintes problemas de " +"dependência:\n" +"\n" +"%s\n" +"\n" +"Execute \"svcs -xv\" de um prompt de comando para obter mais informações " +"sobre estes problemas de dependência." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "Erro do serviço do gerenciador de instantâneos" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"O serviço do gerenciador de instantâneos encontrou um problema e ficará " +"desativado até que o problema seja corrigido.\n" +"\n" +"Para obter mais informações, consulte a página do manual svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"O serviço do gerenciador de instantâneos parece não estar instalado neste " +"sistema.\n" +"\n" +"Para obter mais informações, consulte a página do manual svcs(1)." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"O serviço do gerenciador de instantâneos requer privilégios administrativos " +"para ser executado. Os privilégios administrativos necessários não lhe foram " +"atribuídos.\n" +"\n" +"Consulte o administrador de sistema " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "Caracteres inválidos no nome do instantâneo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "Erro" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"O caracteres permitidos em nomes de instantâneos são :\n" +"[a-z][A-Z][0-9][-_.:\n" +"Todos os caracteres inválidos serão removidos\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "Erro ocorrido durante a criação do instantâneo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "Instantâneo criado com êxito" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "Êxito" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"Um instantâneo do sistema de arquivos zfs %(zfs_fs)s\n" +"denominado %(valid_name)s\n" +"foi criado.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"Efetuar instantâneo agora requer 2 argumentos :\n" +"- O caminho do diretório do qual será efetuado o instantâneo.\n" +"- O sistema de arquivos zfs correspondente a este diretório." + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"Efetuar instantâneo agora requer privilégios administrativos para ser " +"executado. Os privilégios administrativos necessários não lhe foram " +"atribuídos.\n" +"\n" +"Consulte o administrador de sistema " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "" +#~ "A snapshot of zfs filesystem %s\n" +#~ "named %s\n" +#~ "has been created.\n" +#~ msgstr "" +#~ "Um instantâneo do sistema de arquivos zfs %s\n" +#~ "denominado %s\n" +#~ "foi criado.\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "" +#~ "Eliminar backups mais antigos quando o espaço de armazenamento for " +#~ "excedido:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "da capacidade do pool" diff --git a/po/ru.gmo b/po/ru.gmo new file mode 100644 index 0000000000000000000000000000000000000000..878a5b055de84c259ed5a96bbc93500b19d0b520 GIT binary patch literal 385 zcmaivJx{|h5Qd9j%E-*%feo%WsZdiRB%mKqsTEZf*iCW-LuyC9D}o=w-{ZG%5fUAF zrAIgT@%i=l;lWo=vL_q~$HKmFDs;((TR9xb?3}->VtB=pVP3%_MsmR$#cqQ@jjJ%K zyXo~LSz{X_!;)%O!OgZND1uSR>b`(+M9XpuCYI5tOzRP(8R((PGBX&S=#kbU!W!RQ zw|(d3kZRDW%2G2p|MeD@7>Z~;HI_p(szQ3>&_d;?+!9@d7CnW=Mo0Y+>ttbly|pz$ zfp+u1zML KQTdbOr1t~pb#CbZ literal 0 HcmV?d00001 diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 0000000..19fdbba --- /dev/null +++ b/po/ru.po @@ -0,0 +1,489 @@ +# Copyright (C) 2008 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2008-10-01 21:35+0900\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "" + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" diff --git a/po/sv.gmo b/po/sv.gmo new file mode 100644 index 0000000000000000000000000000000000000000..878a5b055de84c259ed5a96bbc93500b19d0b520 GIT binary patch literal 385 zcmaivJx{|h5Qd9j%E-*%feo%WsZdiRB%mKqsTEZf*iCW-LuyC9D}o=w-{ZG%5fUAF zrAIgT@%i=l;lWo=vL_q~$HKmFDs;((TR9xb?3}->VtB=pVP3%_MsmR$#cqQ@jjJ%K zyXo~LSz{X_!;)%O!OgZND1uSR>b`(+M9XpuCYI5tOzRP(8R((PGBX&S=#kbU!W!RQ zw|(d3kZRDW%2G2p|MeD@7>Z~;HI_p(szQ3>&_d;?+!9@d7CnW=Mo0Y+>ttbly|pz$ zfp+u1zML KQTdbOr1t~pb#CbZ literal 0 HcmV?d00001 diff --git a/po/sv.po b/po/sv.po new file mode 100644 index 0000000..19fdbba --- /dev/null +++ b/po/sv.po @@ -0,0 +1,489 @@ +# Copyright (C) 2008 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2008-10-01 21:35+0900\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "" + +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "" + +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +msgid "Time Slider: Low Space Warning" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "" + +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" diff --git a/po/zh_CN.gmo b/po/zh_CN.gmo new file mode 100644 index 0000000000000000000000000000000000000000..27d89a5b912a924e5f9a7da7166254bf640f5a85 GIT binary patch literal 9348 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 +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2008-10-01 21:35+0900\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "将系统配置为自动捕获数据的快照" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "时间滑块" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "%d 个快照将被删除。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "摘要" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "取消全选(_E)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "删除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "正在删除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "正在删除快照..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "完成后退出。 " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "文件系统:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "名称:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "按“删除”以继续。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "正在扫描快照..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "从以下列表中选择要删除的 ZFS 快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "启用时间滑块" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "要备份的文件系统" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "高级选项" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "自定义(_U):" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "适用于高级用户" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "建议大多数用户采用" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "当存储空间使用率超过以下值时减少备份:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "时间滑块管理器" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "时间滑块通过定时拍摄 ZFS 快照来对数据进行定期备份" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "全部(_A)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "删除快照(_D)..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "关于文件系统容量" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "立即捕获快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "捕获以下项的快照 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "名称为:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "当前版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "早期版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "双击以打开文件" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "打开文件的当前版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "正在扫描早期版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "时间滑块文件版本资源管理器" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "比较" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "文件名" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "上次修改时间: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "名称:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "大小: " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "时间滑块管理器" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "紧急:'%s' 已满!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"文件系统 '%s' 的占满程度已超过 %s%%。\n" +"作为一种紧急措施,时间滑块已销毁了它的所有备份。\n" +"要修复此问题,请删除 '%s' 上所有不必要的文件,或增加磁盘空间(请参见 ZFS 文" +"档)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "紧急:'%s' 几乎已满!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"文件系统 '%s' 已超过其总容量的 %s%%。作为一种紧急措施,时间滑块已销毁了它的大" +"部分或所有备份,以防止磁盘被占满。要防止这种情况再次发生,请删除 '%s' 上所有" +"不必要的文件,或增加磁盘空间(请参见 ZFS 文档)。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "紧急:'%s' 几乎已满!" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"文件系统 '%s' 已超过其总容量的 %s%%。作为一种补救措施,时间滑块已销毁了一些备" +"份,并随着容量的不断减小,将销毁更多备份,最终销毁所有备份。\n" +"要防止这种情况再次发生,请删除 '%s' 上所有不必要的文件,或增加磁盘空间(请参" +"见 ZFS 文档)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "警告:'%s' 即将被占满" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"'%s' 已超过其总容量的 %s%%。为了修复此问题,时间滑块已销毁了一些最新的备份," +"并随着容量的不断减小,将销毁更多备份。\n" +"要防止这种情况再次发生,请删除 '%s' 上所有不必要的文件,或增加磁盘空间(请参" +"见 ZFS 文档)。" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "传统" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "挂载点" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "文件系统名称" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "快照名称" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "创建时间" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "全部" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "每月" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "每周" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "每天" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "每小时" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "每 1/4 小时" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "无法删除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s 具有一个或多个相关克隆,不会被删除。要删除此快照,请首先删除从此快照克隆的" +"所有数据集和快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "1 个快照将被删除。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "无法读取某些快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "无法删除某些快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "权限不足" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"需要有管理权限才能删除快照。没有为您指定必要的管理权限。\n" +"\n" +"请咨询您的系统管理员 " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "参数计数无效。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"版本资源管理器需要 2 个参数:\n" +"- 根快照目录的路径。\n" +"- 要浏览的文件名。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "选择" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "快照管理器服务相关性错误" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"由于相关性问题,快照管理器服务已被置于脱机。已发现以下相关性问题:\n" +"\n" +"%s\n" +"\n" +"有关这些相关性问题的更多信息,请从命令提示符下运行 \"svcs -xv\"。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "快照管理器服务错误" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"该快照管理器服务出现了问题,并已被禁用,直至问题得以修复。\n" +"\n" +"有关更多信息,请参见 svcs(1) 手册页。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"该快照管理器服务似乎没有安装在此系统上。\n" +"\n" +"有关更多信息,请参见 svcs(1) 手册页。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"该快照管理器服务需要管理权限才能运行。没有为您指定必要的管理权限。\n" +"\n" +"请咨询您的系统管理员 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "快照名称中存在无效字符" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "错误" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"快照名称中允许使用的字符为:\n" +"[a-z][A-Z][0-9][-_.:\n" +"所有无效字符都会被删除\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "创建快照时发生错误" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "已成功创建快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "成功" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +# msgid "" +# "A snapshot of zfs filesystem %s\n" +# "named %s\n" +# "has been created.\n" +# msgstr "" +# "已创建了一个名为 %1$s 的 zfs 文件\n" +# "系统 %2$s\n" +# "的快照。\n" +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"已创建了一个名为 %(valid_name)s 的 zfs 文件\n" +"系统 %(zfs_fs)s\n" +"的快照。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"立即进行快照需要 2 个参数:\n" +"- 要进行快照的目录的路径。\n" +"- 与此目录相对应的 zfs 文件系统。" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"需要有管理权限才能立即进行快照。没有为您指定必要的管理权限。\n" +"\n" +"请咨询您的系统管理员 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "当存储空间使用率超过以下值时销毁最旧的备份:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "池容量" diff --git a/po/zh_HK.gmo b/po/zh_HK.gmo new file mode 100644 index 0000000000000000000000000000000000000000..641d673a30315c2211c01129778b90d7d1bea41b GIT binary patch literal 9460 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 arR;GUFRI*%s|OjMnJlP^<{@h{~SpG literal 0 HcmV?d00001 diff --git a/po/zh_HK.po b/po/zh_HK.po new file mode 100644 index 0000000..5e0963e --- /dev/null +++ b/po/zh_HK.po @@ -0,0 +1,842 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2008-10-01 21:35+0900\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "將系統配置為自動對資料執行快照" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "時間滑動軸" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "將刪除 %d 個快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "摘要" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "全部取消選取(_E)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "刪除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "正在刪除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "正在刪除快照..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "完成後結束。 " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "檔案系統:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "名稱:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "請按下 [刪除] 繼續。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "正在掃描快照..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "請從以下清單中選取要刪除的 ZFS 快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "啟用時間調整器" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "要備份的檔案系統" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "進階選項" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "自訂(_U):" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "適用於進階使用者" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "建議大多數使用者採用" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "當儲存空間使用量超過下列大小時減少備份:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "時間滑動軸管理員" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "時間調整器會透過擷取定時的 ZFS 快照,定期備份資料" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "全部(_A)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "刪除快照(_D)..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "檔案系統容量" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "立即擷取快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "擷取以下項目的快照 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "名稱為:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "目前版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "較舊版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "連按兩下以開啟檔案" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "開啟目前版本的檔案" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "掃描較舊版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "時間滑動軸檔案版本檔案總管" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "對比" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "檔案名稱" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "上次修改時間: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "名稱:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "大小: " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "時間滑動軸管理員" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "緊急:「%s」已滿!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"檔案系統:「%s」已超過 %s%%。\n" +"時間調整器所採取的緊急措施是銷毀其所有的備份。\n" +"若要修正此問題,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文" +"件)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "緊急:「%s」幾乎已滿!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"檔案系統:「%s」超過了其總容量的 %s%%。時間調整器所採取的緊急措施是銷毀大部分" +"或其所有的備份,以防止磁碟變滿。若要避免再次發生這種情形,請刪除「%s」上所有" +"不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文件)。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "緊要:「%s」幾乎已滿!" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"檔案系統:「%s」超過了其總容量的 %s%%。時間調整器所採取的補救措施是銷毀部分備" +"份,並將會銷毀更多備份,而最終會銷毀所有備份,因為容量一直不斷減少。\n" +"若要避免再次發生這種情形,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請" +"參閱 ZFS 文件)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "警告:「%s」快要滿了" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"「%s」已超過其總容量的 %s%%。為修正此問題,時間調整器已銷毀部分最近的備份,並" +"將會銷毀更多備份,因為容量一直不斷減少。\n" +"若要避免再次發生此情形,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參" +"閱 ZFS 文件)。" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "原來的" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "掛載點" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "檔案系統名稱" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "快照名稱" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "建立時間" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "全部" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "每月" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "每週" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "每天" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "每小時" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "每 1/4 小時" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "無法刪除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s 具有一個或多個附屬複製,不會被刪除。要刪除此快照,請先刪除從此快照複製的所" +"有資料集和快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "將刪除 1 個快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "無法讀取某些快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "無法刪除某些快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "權限不足" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"需要具有管理權限才能執行 [快照刪除] 操作。沒有為您指定所需的管理權限。\n" +"\n" +"請諮詢系統管理員 " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "引數計數無效。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"版本檔案總管需要 2 個引數:\n" +"- 快照根目錄的路徑。\n" +"- 要瀏覽的檔案名稱。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "選取" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "快照管理員服務相依性錯誤" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"由於相依性問題,已將快照管理員服務置為離線。發現以下相依性問題:\n" +"\n" +"%s\n" +"\n" +"請從指令提示符號下執行「svcs –xv」,以取得有關這些相依性問題的更多資訊。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "快照管理員服務錯誤" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"快照管理員服務遇到了問題,而且在問題修正之前都處於停用狀態。\n" +"\n" +"請參閱「svcs(1) 線上手冊」,以取得更多資訊。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"快照管理員服務似乎並未安裝於此系統上。\n" +"\n" +"請參閱「svcs(1) 線上手冊」,以取得更多資訊。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"快照管理員服務需要有管理權限才能執行。您尚未被指定必要的管理權限。\n" +"\n" +"請洽詢您的系統管理員 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "快照名稱中有無效字元" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "錯誤" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"允許用於快照名稱的字元如下:\n" +"[a-z]、[A-Z]、[0-9]、[、-、_、.、:\n" +"將移除所有無效字元\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "建立快照時發生錯誤" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "已成功建立快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "成功" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +# msgid "" +# "A snapshot of zfs filesystem %s\n" +# "named %s\n" +# "has been created.\n" +# msgstr "" +# "已建立一個名為 %2$s 的\n" +# " zfs 檔案系統 %1$s\n" +# "的快照。\n" +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"已建立一個名為 %(valid_name)s 的\n" +" zfs 檔案系統 %(zfs_fs)s\n" +"的快照。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"[立即擷取快照] 需要 2 個引數:\n" +"- 要進行快照擷取的目錄的路徑。\n" +"- 與此目錄對應的 zfs 檔案系統。" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"需要具有管理權限才能執行 [立即擷取快照] 操作。沒有為您指定所需的管理權限。\n" +"\n" +"請諮詢系統管理員 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "當儲存空間使用量超過下列大小時銷毀最舊的備份:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "池容量" diff --git a/po/zh_TW.gmo b/po/zh_TW.gmo new file mode 100644 index 0000000000000000000000000000000000000000..641d673a30315c2211c01129778b90d7d1bea41b GIT binary patch literal 9460 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 arR;GUFRI*%s|OjMnJlP^<{@h{~SpG literal 0 HcmV?d00001 diff --git a/po/zh_TW.po b/po/zh_TW.po new file mode 100644 index 0000000..5e0963e --- /dev/null +++ b/po/zh_TW.po @@ -0,0 +1,842 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# This file is distributed under the same license as the time-slider package. +# +# Desktop Discuss +# +msgid "" +msgstr "" +"Project-Id-Version: time-slider HEAD\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-07 22:37+0900\n" +"PO-Revision-Date: 2008-10-01 21:35+0900\n" +"Last-Translator: desktop-discuss@opensolaris.org\n" +"Language-Team: desktop-discuss@opensolaris.org\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:1 +msgid "Configure the system to take automatic snapshots of your data" +msgstr "將系統配置為自動對資料執行快照" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../data/time-slider.desktop.in.h:2 +#: ../usr/share/time-slider/lib/time_slider/applet.py:185 +msgid "Time Slider" +msgstr "時間滑動軸" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:2 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:242 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:342 +#, no-c-format, python-format +msgid "%d snapshots will be deleted." +msgstr "將刪除 %d 個快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:3 +msgid "Summary" +msgstr "摘要" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:4 +msgid "D_eselect All" +msgstr "全部取消選取(_E)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:5 +msgid "Delete Snapshots" +msgstr "刪除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:6 +msgid "Deleting Snapshots" +msgstr "正在刪除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:7 +msgid "Deleting snapshots..." +msgstr "正在刪除快照..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:8 +msgid "Exit when done. " +msgstr "完成後結束。 " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:9 +msgid "File System:" +msgstr "檔案系統:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:10 +msgid "Name:" +msgstr "名稱:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:11 +msgid "Press Delete to continue." +msgstr "請按下 [刪除] 繼續。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:12 +msgid "Scanning snapshots..." +msgstr "正在掃描快照..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-delete.glade.h:13 +msgid "Select ZFS snapshots to delete from the list below." +msgstr "請從以下清單中選取要刪除的 ZFS 快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:1 +msgid "Enable Time Slider" +msgstr "啟用時間調整器" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:2 +msgid "File Systems To Back Up" +msgstr "要備份的檔案系統" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:3 +msgid "Advanced Options" +msgstr "進階選項" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:4 +msgid "C_ustom:" +msgstr "自訂(_U):" + +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:5 +msgid "Configuring Time Slider..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:6 +msgid "For advanced users" +msgstr "適用於進階使用者" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:7 +msgid "Recommended for most users" +msgstr "建議大多數使用者採用" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:8 +msgid "Reduce backups when storage space usage exceeds:" +msgstr "當儲存空間使用量超過下列大小時減少備份:" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:9 +msgid "Time Slider Manager" +msgstr "時間滑動軸管理員" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:10 +msgid "Time Slider backs up data regularly by taking timed ZFS Snapshots" +msgstr "時間調整器會透過擷取定時的 ZFS 快照,定期備份資料" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:11 +msgid "_All" +msgstr "全部(_A)" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:12 +msgid "_Delete Snapshots..." +msgstr "刪除快照(_D)..." + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-setup.glade.h:13 +msgid "of file system capacity" +msgstr "檔案系統容量" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:1 +msgid "Take a snapshot now" +msgstr "立即擷取快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:2 +msgid "Take a snapshot of " +msgstr "擷取以下項目的快照 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-snapshot.glade.h:3 +msgid "with the name :" +msgstr "名稱為:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:1 +msgid "10 Mo" +msgstr "10 Mo" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:2 +msgid "19/01/09" +msgstr "19/01/09" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:3 +msgid "Current Version" +msgstr "目前版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:4 +msgid "Older Versions" +msgstr "較舊版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:5 +msgid "Double click to open the file" +msgstr "連按兩下以開啟檔案" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:6 +msgid "Open the current version of the file" +msgstr "開啟目前版本的檔案" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:7 +msgid "Scanning for older versions" +msgstr "掃描較舊版本" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:8 +msgid "Time Slider File Version Explorer" +msgstr "時間滑動軸檔案版本檔案總管" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:9 +msgid "compare" +msgstr "對比" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:10 +msgid "filename" +msgstr "檔案名稱" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:11 +msgid "gtk-close" +msgstr "gtk-close" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:12 +msgid "last modified : " +msgstr "上次修改時間: " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:13 +msgid "name :" +msgstr "名稱:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/glade/time-slider-version.glade.h:14 +msgid "size : " +msgstr "大小: " + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:57 +#, fuzzy +msgid "Time Slider: Low Space Warning" +msgstr "時間滑動軸管理員" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:75 +#, python-format +msgid "Emergency: '%s' is full!" +msgstr "緊急:「%s」已滿!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:76 +#, python-format +msgid "The file system: '%s', is over %s%% full." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:78 +#, python-format +msgid "" +"The file system: '%s', is over %s%% full.\n" +"As an emergency measure, Time Slider has destroyed all of its backups.\n" +"To fix this problem, delete any unnecessary files on '%s', or add disk space " +"(see ZFS documentation)." +msgstr "" +"檔案系統:「%s」已超過 %s%%。\n" +"時間調整器所採取的緊急措施是銷毀其所有的備份。\n" +"若要修正此問題,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文" +"件)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:87 +#, python-format +msgid "Emergency: '%s' is almost full!" +msgstr "緊急:「%s」幾乎已滿!" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:88 +#: ../usr/share/time-slider/lib/time_slider/applet.py:103 +#: ../usr/share/time-slider/lib/time_slider/applet.py:118 +#, python-format +msgid "The file system: '%s', exceeded %s%% of its total capacity" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:91 +#, fuzzy, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As an emerency " +"measure, Time Slider has has destroyed most or all of its backups to prevent " +"the disk becoming full. To prevent this from happening again, delete any " +"unnecessary files on '%s', or add disk space (see ZFS documentation)." +msgstr "" +"檔案系統:「%s」超過了其總容量的 %s%%。時間調整器所採取的緊急措施是銷毀大部分" +"或其所有的備份,以防止磁碟變滿。若要避免再次發生這種情形,請刪除「%s」上所有" +"不必要的檔案,或增加磁碟空間 (請參閱 ZFS 文件)。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:102 +#, python-format +msgid "Urgent: '%s' is almost full!" +msgstr "緊要:「%s」幾乎已滿!" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:106 +#, python-format +msgid "" +"The file system: '%s', exceeded %s%% of its total capacity. As a remedial " +"measure, Time Slider has destroyed some backups, and will destroy more, " +"eventually all, as capacity continues to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation)." +msgstr "" +"檔案系統:「%s」超過了其總容量的 %s%%。時間調整器所採取的補救措施是銷毀部分備" +"份,並將會銷毀更多備份,而最終會銷毀所有備份,因為容量一直不斷減少。\n" +"若要避免再次發生這種情形,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請" +"參閱 ZFS 文件)。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:117 +#, python-format +msgid "Warning: '%s' is getting full" +msgstr "警告:「%s」快要滿了" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/applet.py:121 +#, fuzzy, python-format +msgid "" +"'%s' exceeded %s%% of its total capacity. To fix this, Time Slider has " +"destroyed some recent backups, and will destroy more as capacity continues " +"to diminish.\n" +"To prevent this from happening again, delete any unnecessary files on '%s', " +"or add disk space (see ZFS documentation).\n" +msgstr "" +"「%s」已超過其總容量的 %s%%。為修正此問題,時間調整器已銷毀部分最近的備份,並" +"將會銷毀更多備份,因為容量一直不斷減少。\n" +"若要避免再次發生此情形,請刪除「%s」上所有不必要的檔案,或增加磁碟空間 (請參" +"閱 ZFS 文件)。" + +#: ../usr/share/time-slider/lib/time_slider/applet.py:137 +msgid "Details..." +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:116 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:306 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:106 +msgid "Legacy" +msgstr "原來的" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:147 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:131 +msgid "Mount Point" +msgstr "掛載點" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:155 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:134 +msgid "File System Name" +msgstr "檔案系統名稱" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:163 +msgid "Snapshot Name" +msgstr "快照名稱" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:171 +msgid "Creation Time" +msgstr "建立時間" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#. Note to developers. +#. The second element is for internal matching and should not +#. be i18ned under any circumstances. +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:185 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:198 +msgid "All" +msgstr "全部" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:199 +msgid "Monthly" +msgstr "每月" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:200 +msgid "Weekly" +msgstr "每週" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:201 +msgid "Daily" +msgstr "每天" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:202 +msgid "Hourly" +msgstr "每小時" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:203 +msgid "1/4 Hourly" +msgstr "每 1/4 小時" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:223 +msgid "Snapshot can not be deleted" +msgstr "無法刪除快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:224 +#, python-format +msgid "" +"%s has one or more dependent clones and will not be deleted. To delete this " +"snapshot, first delete all datasets and snapshots cloned from this snapshot." +msgstr "" +"%s 具有一個或多個附屬複製,不會被刪除。要刪除此快照,請先刪除從此快照複製的所" +"有資料集和快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:240 +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:340 +msgid "1 snapshot will be deleted." +msgstr "將刪除 1 個快照。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:381 +msgid "Some snapshots could not be read" +msgstr "無法讀取某些快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:405 +msgid "Some snapshots could not be deleted" +msgstr "無法刪除某些快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:539 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:422 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:215 +msgid "Insufficient Priviliges" +msgstr "權限不足" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/deletegui.py:540 +msgid "" +"Snapshot deletion requires administrative privileges to run. You have not " +"been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"需要具有管理權限才能執行 [快照刪除] 操作。沒有為您指定所需的管理權限。\n" +"\n" +"請諮詢系統管理員 " + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:246 +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:247 +msgid "Hint" +msgstr "" + +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:248 +msgid "" +"Installing the optional meld package will enhance the file comparison " +"visualization" +msgstr "" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:316 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:179 +msgid "Invalid arguments count." +msgstr "引數計數無效。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/fileversion.py:318 +msgid "" +"Version explorer requires 2 arguments :\n" +"- The path of the root snapshot directory.\n" +"- The filename to explore." +msgstr "" +"版本檔案總管需要 2 個引數:\n" +"- 快照根目錄的路徑。\n" +"- 要瀏覽的檔案名稱。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:128 +msgid "Select" +msgstr "選取" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:154 +msgid "Snapshot manager service dependency error" +msgstr "快照管理員服務相依性錯誤" + +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:155 +#, python-format +msgid "" +"The snapshot manager service has been placed offline due to a dependency " +"problem. The following dependency problems were found:\n" +"\n" +"%s\n" +"\n" +"Run \"svcs -xv\" from a command prompt for more information about these " +"dependency problems." +msgstr "" +"由於相依性問題,已將快照管理員服務置為離線。發現以下相依性問題:\n" +"\n" +"%s\n" +"\n" +"請從指令提示符號下執行「svcs –xv」,以取得有關這些相依性問題的更多資訊。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:169 +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:186 +msgid "Snapshot manager service error" +msgstr "快照管理員服務錯誤" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:170 +msgid "" +"The snapshot manager service has encountered a problem and has been disabled " +"until the problem is fixed.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"快照管理員服務遇到了問題,而且在問題修正之前都處於停用狀態。\n" +"\n" +"請參閱「svcs(1) 線上手冊」,以取得更多資訊。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:187 +msgid "" +"The snapshot manager service does not appear to be installed on this " +"system.\n" +"\n" +"See the svcs(1) man page for more information." +msgstr "" +"快照管理員服務似乎並未安裝於此系統上。\n" +"\n" +"請參閱「svcs(1) 線上手冊」,以取得更多資訊。" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/setupgui.py:423 +msgid "" +"The snapshot manager service requires administrative privileges to run. You " +"have not been assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"快照管理員服務需要有管理權限才能執行。您尚未被指定必要的管理權限。\n" +"\n" +"請洽詢您的系統管理員 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:116 +msgid "Invalid characters in snapshot name" +msgstr "快照名稱中有無效字元" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:117 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:147 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:180 +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:216 +msgid "Error" +msgstr "錯誤" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:118 +msgid "" +"Allowed characters for snapshot names are :\n" +"[a-z][A-Z][0-9][-_.:\n" +"All invalid characters will be removed\n" +msgstr "" +"允許用於快照名稱的字元如下:\n" +"[a-z]、[A-Z]、[0-9]、[、-、_、.、:\n" +"將移除所有無效字元\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:146 +msgid "Error occured while creating the snapshot" +msgstr "建立快照時發生錯誤" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:155 +msgid "Snapshot created successfully" +msgstr "已成功建立快照" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:156 +msgid "Success" +msgstr "成功" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +# msgid "" +# "A snapshot of zfs filesystem %s\n" +# "named %s\n" +# "has been created.\n" +# msgstr "" +# "已建立一個名為 %2$s 的\n" +# " zfs 檔案系統 %1$s\n" +# "的快照。\n" +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:157 +#, python-format +msgid "" +"A snapshot of zfs filesystem %(zfs_fs)s\n" +"named %(valid_name)s\n" +"has been created.\n" +msgstr "" +"已建立一個名為 %(valid_name)s 的\n" +" zfs 檔案系統 %(zfs_fs)s\n" +"的快照。\n" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:181 +msgid "" +"Snapshot Now requires 2 arguments :\n" +"- The path of the directory to be snapshotted.\n" +"- The zfs filesystem corresponding to this directory." +msgstr "" +"[立即擷取快照] 需要 2 個引數:\n" +"- 要進行快照擷取的目錄的路徑。\n" +"- 與此目錄對應的 zfs 檔案系統。" + +# +# COMMUNITY MESSAGE +# COMMUNITY FUZZY +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#: ../usr/share/time-slider/lib/time_slider/snapnowui.py:217 +msgid "" +"Snapshot Now requires administrative privileges to run. You have not been " +"assigned the necessaryadministrative priviliges.\n" +"\n" +"Consult your system administrator " +msgstr "" +"需要具有管理權限才能執行 [立即擷取快照] 操作。沒有為您指定所需的管理權限。\n" +"\n" +"請諮詢系統管理員 " + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "Destroy oldest backups when storage space usage exceeds:" +#~ msgstr "當儲存空間使用量超過下列大小時銷毀最舊的備份:" + +# COMMUNITY UNTRANS +# SUN CHANGED MESSAGE +# SUN LING REVIEWED +#~ msgid "of pool capacity" +#~ msgstr "池容量" diff --git a/py-compile.py b/py-compile.py new file mode 100644 index 0000000..9be615a --- /dev/null +++ b/py-compile.py @@ -0,0 +1,7 @@ +import compileall +import os + +destdir = os.getenv("DESTDIR", ".") +dir = destdir + "/usr" +compileall.compile_dir(destdir, force=1) + diff --git a/usr/bin/time-slider-setup b/usr/bin/time-slider-setup new file mode 100755 index 0000000..d64f0ac --- /dev/null +++ b/usr/bin/time-slider-setup @@ -0,0 +1,13 @@ +#!/usr/bin/env python2.6 + +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, 'share', + 'time-slider', 'lib')) + +from time_slider.setupgui import main + +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) + diff --git a/usr/lib/time-slider-delete b/usr/lib/time-slider-delete new file mode 100755 index 0000000..cc55e44 --- /dev/null +++ b/usr/lib/time-slider-delete @@ -0,0 +1,13 @@ +#!/usr/bin/env python2.6 + +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, 'share', + 'time-slider', 'lib')) + +from time_slider.deletegui import main + +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) + diff --git a/usr/lib/time-slider-notify b/usr/lib/time-slider-notify new file mode 100755 index 0000000..bd503a0 --- /dev/null +++ b/usr/lib/time-slider-notify @@ -0,0 +1,12 @@ +#!/usr/bin/env python2.6 + +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, 'share', + 'time-slider', 'lib')) + +from time_slider.applet import main + +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) diff --git a/usr/lib/time-slider-snapshot b/usr/lib/time-slider-snapshot new file mode 100755 index 0000000..33406ae --- /dev/null +++ b/usr/lib/time-slider-snapshot @@ -0,0 +1,13 @@ +#!/usr/bin/env python2.6 + +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, 'share', + 'time-slider', 'lib')) + +from time_slider.snapnowui import main + +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) + diff --git a/usr/lib/time-slider-version b/usr/lib/time-slider-version new file mode 100755 index 0000000..aca093b --- /dev/null +++ b/usr/lib/time-slider-version @@ -0,0 +1,11 @@ +#!/usr/bin/env python2.6 + +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, 'share', + 'time-slider', 'lib')) + +from time_slider.fileversion import main + +main(abspath(__file__)) + diff --git a/usr/lib/time-slider/plugins/rsync/rsync-backup b/usr/lib/time-slider/plugins/rsync/rsync-backup new file mode 100755 index 0000000..1129617 --- /dev/null +++ b/usr/lib/time-slider/plugins/rsync/rsync-backup @@ -0,0 +1,10 @@ +#!/usr/bin/python2.6 +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, pardir, pardir, pardir, + 'share', 'time-slider', 'lib', 'plugin')) + +from rsync.backup import main +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) diff --git a/usr/lib/time-slider/plugins/rsync/rsync-trigger b/usr/lib/time-slider/plugins/rsync/rsync-trigger new file mode 100755 index 0000000..49e863c --- /dev/null +++ b/usr/lib/time-slider/plugins/rsync/rsync-trigger @@ -0,0 +1,10 @@ +#!/usr/bin/python2.6 +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, pardir, pardir, pardir, + 'share', 'time-slider', 'lib', 'plugin')) + +from rsync.trigger import main +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) diff --git a/usr/lib/time-slider/plugins/zfssend/zfssend b/usr/lib/time-slider/plugins/zfssend/zfssend new file mode 100755 index 0000000..17dec8b --- /dev/null +++ b/usr/lib/time-slider/plugins/zfssend/zfssend @@ -0,0 +1,10 @@ +#!/usr/bin/python2.6 +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, pardir, pardir, pardir, + 'share', 'time-slider', 'lib', 'plugin')) + +from zfssend.zfssend import main +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) diff --git a/usr/lib/time-sliderd b/usr/lib/time-sliderd new file mode 100755 index 0000000..eccac9c --- /dev/null +++ b/usr/lib/time-sliderd @@ -0,0 +1,11 @@ +#!/usr/bin/python2.6 +import sys +from os.path import dirname, join, pardir, abspath +sys.path.insert(0, join(dirname(__file__), pardir, 'share', + 'time-slider', 'lib')) + +from time_slider.timesliderd import main +# Pass through the executable path in case it needs to +# be invoked via gksu (user has insufficient priviliges) +main(abspath(__file__)) + diff --git a/usr/share/applications/time-slider.desktop b/usr/share/applications/time-slider.desktop new file mode 100644 index 0000000..dae4339 --- /dev/null +++ b/usr/share/applications/time-slider.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Time Slider +Comment=Configure the system to take automatic snapshots of your data +Terminal=false +Type=Application +StartupNotify=true +Exec=time-slider-setup +Icon=time-slider-setup +Categories=Application;System;Settings diff --git a/usr/share/icons/hicolor/16x16/apps/time-slider-setup.png b/usr/share/icons/hicolor/16x16/apps/time-slider-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..859149532e43e76144cfb110761269996d37e246 GIT binary patch literal 3383 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 N002ovPDHLkV1fW`Nz4EM literal 0 HcmV?d00001 diff --git a/usr/share/icons/hicolor/24x24/apps/time-slider-setup.png b/usr/share/icons/hicolor/24x24/apps/time-slider-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..d2e3f78745012bd1cbf9b6efd37700b3aaf816dc GIT binary patch literal 1304 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 literal 0 HcmV?d00001 diff --git a/usr/share/icons/hicolor/36x36/apps/time-slider-setup.png b/usr/share/icons/hicolor/36x36/apps/time-slider-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..9d893b15f525fbded609fb336ca74966843bc7a1 GIT binary patch literal 2151 zcmV-t2$=VYP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOW1 z2_PwLAjBj900-wuL_t(o!@ZbYXk5n?fWNu_tKGG%t{iMtrZu%5oZ?1O?AXMRJOrE& zZIYK#$V2LeQb@^@pbsJKQ^BQgH7&R`G^HX?K?^QuwU2Elpm}KC<-krH~zH`o;Gc#8J;;pxSdcgC%gxLX`Y+yl?~gMM;3}`%}Y&J_ao85N4d+YbG9S5aU8<24TN6JXwU%yWjM%%t9 zrR472J3R5k6TJ57s~k9R0LO6Qpngc~q#M0s-M~@!irI%hJm&*}FQKG;Y!<8#nc;ST? zl5j^+L|0cAd-m+%!w)}XY;2rwc6Q=KUB0Fsg@iU`B7DbV$q8IBxzhGMZu zKA$IwB34&d34$OQ^Vw&gp_EFVS65eQZ*Qlqt&L)_NTpI?eEi>hXaD}D2FFrv0*Yhg zHb5&YE3~&4=3DpM0F2Y07`45l!RfJl3TZK(cRt6!Gi}0!;ty;d9>DB#27=lT*esFw10kn zo-hnKc<>mB`r|l8>xY1187B*?(JGY+`}XZ?S|m3YJkRr}R4OUR zru&2t?Ay1GN~Mz070Vc<^g|ws<0Os`$R&kB0U^W|%i}msf!AuaZ9TLZs8A>n$1A^5jY8=H^mkfA?)Tefl*0{ryd0hKGj{LeSIG zgVs9jp@)IA)*Gq6$?AqhD=RA;J$e)&MDk*Pe?MUuCLXEPYW(xkC4T&m-w;J_H{HLU zefC+5G2FOu10h68mr^QOLskPw#xYXLlpq9)i;Jlg)>;#WA+=g9$q*?e`}gnX-p>c9 z_V+bS{e~0V+l%8kTf}u;GMNmyTn^v&F~;D$bB6f$@8UQPy}iBU^LfsnKhM>x*D&>k{74|v zq{3R3h1QzU(NWgc*68f)#P@w%*KI065Rl8|$Y!%xmWB8BuQ7jkm(I>k*4EY-9UV>0 z`>-TH#u#*yvTuyRVEA}qg2BN-Zr;2}5Cl}KRfdO$w}r}Pvy6?6Q7VBnSGs6!8q9|l+Y@F5ARbG7Y zMP7gX_0*VMyLP3X_w3oj;NT$V&Yfd=dIqHxeh@URQJeoufEo*QyXH6!VHh$sHO1WA z9OurRqo=2b-rioiy1ID&`R7?)US@iFnoE~1aryFP=H}*zq6maVOG|d!1Hau2$g;#E zff}1=RKI0RPtWk_r=Rk{2OnVDcCz28dQFLAg%ASY5Ab~dk@MP{^d`S-+u8OIzPY)s zi!mmtu(65>BZ<)(VOiL=g=0JTK?c`#A9WT1Oo#DTl!9%IKh)oKAAxL=cA9B zd&seEJkP^+90msdTt9w%x$C{wn)~bPOwTTC=^Gjv;?m{ISeC_`Z@!7=c{on9ka-?K z5a78kJ6m^dOlDY?JM>bdE<>YXlZF-Xm}Xk_v@F(jfLAdpw*O8+VebXa&nTXsj0^Tl2UTv!UfL$ z{VW2p@gc>4!LVaTD@yAmTRqRCR4N%`OcgK=5SPp43nL>V&aq?1zV&&v=!-7uHVFefcp4j1i{E zMdGy^2Qu|_^>qSi dpaxU{^?xO@8sP9kp*;Wq002ovPDHLkV1hlDDlz~7 literal 0 HcmV?d00001 diff --git a/usr/share/icons/hicolor/48x48/apps/time-slider-setup.png b/usr/share/icons/hicolor/48x48/apps/time-slider-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..440fcf1acb177cd0d1e261d2b75b93fb44b28706 GIT binary patch literal 5750 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 literal 0 HcmV?d00001 diff --git a/usr/share/icons/hicolor/72x72/apps/time-slider-setup.png b/usr/share/icons/hicolor/72x72/apps/time-slider-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..5b0a5cb8f4a7ea827d01a6668672d8dbfd7986f3 GIT binary patch literal 8633 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 literal 0 HcmV?d00001 diff --git a/usr/share/icons/hicolor/96x96/apps/time-slider-setup.png b/usr/share/icons/hicolor/96x96/apps/time-slider-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..ef6e07fd5d67d8e87011066ff0d55e250e19d5e3 GIT binary patch literal 11053 zcmWk!1yod96deTV?o?VNM?gZl8>FN`x*MdsLHg4T(v2{5x3oxiiFB9Lzp~cw)|z>5 zZk%)W-e=#5_^2p_fl7)Bfj}^1q{UUh_4B_MG9viDnd-&^E+`JtTFwv%8s5Jb93(xH z7y?06vJ?~h_|d}N#opP%-hom^OpMaO$==M;#uNf^U(8Z9S5w_16u4Qv6qO72|4+_d zg#ejSMKsDEKc1eE3I$s>oGNdDK)D-7LIQzoC^sBFCdU6Ofig3CBb6Wex!ke-1_qKGf{UTbwJ?Ur_qohT?!dpQ1Y;6tbNfmT{!<>xGtA?9yw%xe2B1T zjA#)=SQ5@ZE0taql8XTOJYsCL1^LJV`TWN3_cX*m>o&~`4x;lP6#-msJcJU@G+Z2F z!3U|B_!KDt(S$;=JNE#ZfuNcN-=( zaAK`rj6xu}@dRMC7mx1am{sHBTrn_A1D5?x#Aj+Fqphc%iE;;F2xP;>cj}3m?K`PI zAELkQQ$F1>f`vX>uIo*dSsj*e10;8EQRBkl-`+^%#WgQ1>}+q(%l3)t82-@kc{1-Y z=+bz!x%1_JxVc*I*rW|$*9(wBxLxlVy_7E`pGZInHd_82EA`Nb_WVFGP2DGB)U3&f zzom@p7$ctk^@6>SIzlwzZ9m=5XOqn}=6iU-wfB%3D?vD~c$N}9WB6;iH`tmkU7I%$ z$a$-M_b&!ycz=tajS08s6QM_`TsnxqnM{Hs1fnla%c}O{yU-vq1R|arz*r+fcG*qL z+=WQq{c^b*?cp<5uqbU`k0`b%s);|j)91GpfuandV>J|vpV@v1Q*d@ETZAS$V6yh8 zH(?4mVEr{j&hBPt3qU~-8A8T0q*@GzGm6xti9sex3%RB0mPWvhqNN-T$5*D7i+?Zu zP9;)}QeB$jT)^oCZ983#F|UENrNTx(I!n?-lfM9 z=4MPf)sqT*<;vTgEVsZ~j1?3cgm(QQM&jtRfwB(tiq`Tlqa+%S)KXWVmk*HEe5!zN zjkq$NsC7fY4Up(U;X-f|r8ImcBCRQ}sW>lPLP<``iL;2pjRGI6-1CMyu|%$(z6Iy5 zkJyO)U6`gcGtDe+EFK%SSGZUoH!W_282g*%xEC_D-_(BM{*?S_r$V#GT$;ftO-hqE zyl7%whs66IGbPhd$adCt?>5Ia**4X=4r-Q>u;aH|)lCMKA6Bv?JJ>s%JBX&K>>}y~ z=_)HFd1^TCW3@#q^I$5~%3&pVoUZA-V?l&6a)p)oNz4!e)qh-Ca8{CDDW*(n z{2bYn`jhep{?ZK{Ba|R$Fx;GhB!d`<*qp?TxH?t7h-5m7WKa#t{q7)jHw`PzUR{$B z#^_BD{{uBiHiEgxA*8KT$JFJwAz5ClxLc=P@o{Q)hrWNdynXSiNBizyav!!wwMW~(#aB}3nuy=gX%Uyu z$BFs--#gm#Q?Ly#ohggy^$Lp>8YUU0Yy`(QPw^MbNJdLyeYG6W-J;#H9k3lJphu*q zqL)+HS2#{rQD`oDTf`^NCqFngI+i~+oAy3K;N2YiZpLoLT*g}CXKi^WV$yBCp zTf0c>7}jPNqRp#SsHv{4_g%6wtCDWEwX(Gsr?^=$QL#LG*1Wgo3^oiaZ6vALnMYiz zg4x1+49$&*%p3cwdXE#^6Mb5Iq8|m2Fv1ov%PF)ee4Xo^@IQR|u$n%ec`W49f0?|; zX0ADbJu%O*1;ZP8+h>`jnlUY9PGsKf9CQ&L6(JWm!kQw;nn5vH9&x8eEo zDl6lAM$b{nQuQ&{vE^~ta?U7c&f9D)4(G|})#L9*!=;HnTR-w!DZ^ z*@pBSJo3hSE<=h5OIdN5KO_swhb!{@cPfI-SVH)f?i|mJE(X=3hoZ-vnr(_}Dtul{ zm+_V2xiq7KXTo88+j|e%evU%so#x;-rEzWY81gF_AF>J4zfjt;3w&sQ7nBem=O|i6 zrH}|S-#7nl_GP5PG^FmS-!7_=?Nt)eu=V%2ikDg}s4&r@KWf?xbBsuJ=B65EonsL{ zc$3SsnZ}MrzyDZ@bs}SVG+*c*zv{39*8| zDH6tLJEK&7q>)nP+fMJPU>%^}{Uflu0nYjWO&xV7#OGjn?nqBblTB-_!SHWr7MUPH zL}V5I5>X0)-#o*z`wm%{{g2MH<^SqS%ln#5Cd_`C?fqV5$=1P4E=&2Hf@z1buvm|= z_No5p>RMunEoF>hsWk{D)?CoZ-_&MWJUl+eC#3dG?NaTp8rSj9l?KDPTf3=aGOxC4 z&cm4{owl1t!QqZ2bvSi}8M7JFdzt(1H$qhrWqY^D_qKYKinfkNgYZA$ACYJYU!FJjBDe)Y&%h$#?u6y3(a$dKzgQwA?kzW15z;@qd zpZ_UwT4}H~vo*b{S=YMx*t5OC%j{;4ESr0L<+9!5jPQAOCYx7arTxOw{-*!tYK?Hk z*XhmU_(5DYsT;(QpoypWt5__t zm$7NFBcT|f2iZJ=yu6!2PLF#J#bOgC6ZF|cPx((HRYZhrzPB?6bK_H~Q+X-pDT$r7 z-i43*T?Zu9O9MRvuR4a^Io|1Ce!4&Ec3XC%I4M}eQrh;W^kaCoxU0QVoh-gjBuPws zMu1PfcrfN*Sps^K(pXwW0Rr))gFyU)AdtIfaNUDIT%i!i{$~i}Ln;J9WS?NrF9~kd zlo1zEb6-5nT5u+tYu^=WheRhKpd5dbj$brg3CA@ZS@gEg{6nASxZPJJuaZ=j_Mf$i z;;Y|SsrT&9UHs#!R#ruJx@pVJF-NtuBN2GK`VtcQYLPZ0bqs0WngaOXu;r>RAemaWnJ|%RB6}V9#Z$ls3A8B4 z5MN<$N|5Zn-}*F*Z5^78e(1T}{o<_V%_$X&rd-x2ZTC{eO6`g_BKho=vsB3ReHUiApk1n{88Ew!Js^CmeOECyH*sUc6 z|E=eBIj3*}t(v+-0xiCTNA6c?`8R*|_eGVHBq=q6eY7n6Q7vMQ;zoW$UACAKuU~A~ zGFyj`Y;qNbG`_l>}kNW75+O(x+r4-NVXWy;tFe)bw$n|vM|JLOW0 z0&9-f=b!dVTt${GKQTXRC6}WlTjrNBy1Ke@a&zOw4whC|zrl^bMn*Wjf4ueX=t01M z*P9#Oar!oGNk~MLmXTrn&dxT{=L)kv=a$O}ZB0sXEy9E=10j_==!Vt`qW*{iR zbALQYp#?6%qAiKHU9;Tz z$74DKUwLffq^GAt(cyccLuTLSVMk9o(yb{tf$O47eQ~B~pP@tC9cOOn=;RtXMZKZyWoE_0e9g`IELA3vFh43oD$eV2uMCJ z+#m(&$~S(>9e|?aOBk-78e&p>2rjbRcH#wjo0gjT87xDSdEokJiG-M#k~F%?D@S(j zo66gP!9fxd5<{?=$;pI^OG{_>kL3F&=4-1y-VbMkx~*R@rlDtd-t&>sMhjJX^NhAN zEM(>KO(UFL19dFSOD$}1Tk~9@mPW*gSy@^9j{EqwwzekhB)@len>{Y*Lk(-}*8~v} z5lgD84UR%RUlt90%%!%GP7tTXHJr#6I9zE>0n6!lIG3x^Z;vdpJX~$h0DB*SH@J6j zu#@GsW@==VpR1MpwZ1>jWZ@;L+8hLn)&Bh%%SaFN=1oDY;E%0Xp%}~b(Xp|2oSc25 zqv&sxvQ6%L6^ybpCvk`L}8XBkrXk|n}sWXqT zrx(?_o#=addfxsih;=_|nB#HWM8Lqn$mDSh)T{^)dfZc*hXs|D&C3SR-~2#W(mq-* zNrL`pk-~7uB-qR-3b7;|s_RMPSSzkAtzV8d8#^Caz~8^!&*F2#Mh)R{d*b&H zh%Nj3G3u2(m46~Pib#2g4r^yu-KZC7k!9S(uAckQt~2D}G)ySBHZNdr&(in#er@aG z&i(0b*V4+Wr;eFD4ILynh-Pwfa#;~|TQBTrQ{cR6RxOAreigejN1lGzgk3XPLQW2C zLg<+vV&8U#I6Xbh`M&?I8!MYtSWi%c{mnEdmlBN^2I4#{gqwJ26pDc-#=UH)!=;qN zE5<-f8!zo}eW(TU>2@mCce^Fi2xKrAcVuM5ggq@1fgqSDA#-GBXvc}JTd(27l%3?~ zUcDFLS+F7T?W3>nd1B7w+Seis7N@yHS4fzG?*XT7hV;@)Jt_e8^?_jbW-O|%AfFXCkj<7guQs1;eV{!W(a zh*9k_?}h5J4b@es@0mNoY%@&Q_YfTbMoK4u@l(yZAJx>8ri+yd7*N?dAK1;uGxG`y zL#ChI)6t0Z*`Rfrn<^CMz1O+82&LszL)xaL))@6|k{WZaF|P!E{Ht3svaP}t{ms!d zj?53PKeFnfqk$hc98z6A9Q_XCRA4$gVtUujIi{{@P>L8QPrtGlT2=-CMk6B_i6QlL z%-%QATX158t#g!Pf?v$Qo=pl7vFW@UDdcbdAjhI~R(D-R!Lsp@+cWk5N?-mE*0k@vww6#++XhN(aE2tf7y{e4-%Y6K1Nj4o?fKEuO#O6T_9s{?sOMI$Xjcz7w) z>&ibi+s+>Hy%kwzv+4SikvLVZ#6=N?>3>24?1(wJ1{ON~gf1>F+MfO%JUr|Q73bvz z=(6tiDDs|-z*k*Sf3}Yw$S^2VtE<=+Ny-6%g`zt(lPf4F@ZT&u|9fb3bkt$Ji>A*A zGqTX(4hguR7x5Su|riO~hIdkagWr6Q?1OsLJTx*$VnORvyic@#r-tss*J6FBC z#5HDl_F{|xn!f2M-`mBG#tFqJ z;A-Ku{#IbS#0t%e+*Z4^ra+oz6rKW*o0^^d8|*X4|45*g>K1Hda%p9Q$HvCQ3zUfh ztt{nAw^2~4jaVWKCVU9rMyfIPkB)vNV}bx01TX;-I+Ncc;U9-e)An@uc->v-*ay+( z$+9Qg(p2IR&A)1FWJln|R8&@&CHwNOoq2pp8jNTMG)%M4Jy)?}769_bbzIaCU87wm zUP&n_AZ>I)R^^${L#Dx+(?+v6ksWb)&&aQT!pR?1@&>Su{L{(L>f(XZKVS^->EcmCK2njXy^;w5KtLU0{ za1Ai^c1S4?qi($fDzWEK(20WWEi5Q;BbugsSW^mGTQfR31SKUUEi5c@3kni-8ts~= zYL9rVKTR=`7&h=n?WsZWlT^!@n#{Ba`TqV*&C1%Dt<=fnbwxGRv}@4&I6IM=_6Za&ZyLCT1@uSf(Qci2xAh@q6Oe)6=`W zx^mhYz-T0rd9nTeco$#CdUg_0Y(_>|^__w&XHALu(*$4{0G1SdUjUP=ct@uomzDio zUe*<@xmv_1-|#DZqO`}?Emy$C&;hZuJLZzbn}K36VVCieLQ1wJ zvaA(Nv?|ao*_I`4SY@C+{PaW-ExA%v4>FeMVx+J(9ZR2O&A>PMhND_7*+3)Ww828; zuKkChEb;vKxME{aL38thSjr3A*>5@Rb{`h?ld4N}by?A;q49B(t`tkK((-cW6|RPP zovkbR*$X?cN?EAdo-;t{Bl^@!TEQRwv zqn@~`^>92~>v1EddMT>YoQ++|mVrCs<^9+i#*%cY44vkMzb-wmi&X5S`=Bt})`eey zH_n|rC7f-@q!Mx$+Ziniw8s!Dh~VdAwwySVWaAZ!^-8M=7pjP`E9s67-VJ7~Rg2$h zb9R;UCLQ{R69t_0%)(F4m9-?9ux@mr(0xj9^u>jB~Y&B@w-WZL5hK;h6Q(!nHn2=dKk zCanz(yMxXW_{vTY^$%;HJpFGy^Po9g2AWB57^oeU~#>BSIdY91_DC&10SCp zGVOTh{mKc~q3BcNYR_F~&b`L<(LGx^^~P9JL1JQ}9d1?I!@}I$E~>w&7N|#Z5v*Ym z#+VCFt8EO7-0`xg}zsq4qsE-kltIQQy# zcsdGBnNXcq)iV~gU1}X+N9zEk)ZE;xK4F($wB$$oo8Bg84v|Nl_~chYWCVJ5yV~ z(i?qx!)WAJsUNq+W3$%L{*9b5JwjqpY|eEKljqfkbsp({BkAkfvxvOy5JrX6H@WM)K zTsEo3%FciV*LsO-?SAzJq8ToE7S{8t{jO8w%J9&q{`XqhrFYWLH%6dCfAD>dY$MHOKus@7YD%2ZRTN>+o6|#4#}9dU8XUHO z3-tSIKsik8g=jS%o{ZP25~@CFW@s27OxbmQ?qrB*3bPStS^17wfi1>N!cu`8+Ygjv zX~K?p{?zQ5OF|Nv%-M5Ke|Zwzi&b&eB-^F!8*(@qdWSOuRNlWnv3AJ5jPKD>i}tSB zwUT$(b8DQWZXb1W;gSfYtFOdfHd?)Wt*%a&CZ8Zr585e&7Xj)}x^NbiHhnA;fu<4! zttzTZm!>_5VXTh3>TNjVllb)1bstH#hw!zr9VgmEnV)-{c^^v<>}*@teXH!@XQ4fT zQ4LEFq&UC_U&vy_uS(LnJPPHvBM@k~5~|NyqV>6zEpqB_kP{FVbvofQaWg(fpRP_Y zs;?nDMZNmv@3upx{<+Yg{W+2vJ~w3f^0UW=laS8Hp<&`9POdpDXb zE6ue~D6`0V`*Tng1F_fZ$|mn&C_123uxl=^t{WG3vOlr&m0UsKfZPlX3tPE*QC9)s zNoQFF8Ro0eDF?FD|1oCq3QB#^!5(zXW&5-qDfXjApAA zefUX~d__}O?K8s=YQT;D%g)gDgO4&_fz)$5S=|8&n<26Xz!YFKIvg*-9CIfp5I`{< z9Wqr_)t8oEWG(Bm5p77|f0!M6B7tmo-~rU}PgB_C>>M6e$hikBb$_lg;Zy2Oa%$AD8AHKmVQ2TnI9#?&+uC|qyVR7; zH=`Ac=I*^{iauSnCC}1L>*W0Ov=|S$nT5rWUIA1<6pMrt{=q}(dEHv7`e(ik< zH>`#_2q%#&hkgKW+7(H~s;HYx^6J&^p?I31x6ht*b$y8*AmmGI}W%yylCtvKzKaK=0z>GC4m#^yd#lZ*MO!0p?=pa8ZaB zLExe+QCkr_xe`Q5Hbd>mo#|v=Xh{xqLs5N_xb2qLy8_^W(GU&%j+T}dV7}Nc!KUWt z|Cr&nzY1$?o?&Xv2>EvYiI@$q;^@BCN09oYBe3+&(et%q2rz{_y}W?^w0peL$`jKI z0s>-L*x8w5Jy-RPn;R+NYZ;?-cB&!AVDh|w0(JP2!_negSBPIqDv=~#I z7$bGE`+fO=zZ>9ZTIjfX{m<{4CKlWva?{A2euUE}Z`K-?X83E3<*colfYh*ilUY`# z+v!UY$2d!ItOd_C0+D$!_*Oiw$Tl%G+swfs?oEz&LKG1jJ3s$uwt(02d(HHs=2ObL z*qx)5nGdPlb>_N-E@5tCHNV^-oY9ko7t%3eMb0VK4&N7;ffxsF1eBL|xUu7k<>_`% zh@FEYXrh{+*1*a3RPqBG3_b7@JtNIXNUE?hs=MdT4+4<8gFxdqdtUwX+ zAv+X7n`xQ;H73T0c!1H*yPedL7v25QTW=2ZlD)eZ|K!pf)sX!X{2&Fj#QSuNxi0GLXD(W6<+PB(!HspT2HotS8<1wGT^Y6?HltZZZyp4 zgDxuw(5m3yKQwOKOtpyR-XW3DWdrZCmyPyoW=>9ZCMU1+MDmHLpQR~Xajh>h63(RHvOjZA- z$mafGGVPL0_n`YW#~IBQ7RC!yXc#6%4-|dX^MD1&{8F_adYMPIH2_S}F)*k`>M}+z z?jMh_`!2IbQ1pBW{i4SB0rY{|P{;_E-xc*HGsu2$J2pBKdT!3eS-TzJ{wHjKEVpx= z;G6Rj*uKJ5YlsPyQXMUn#`Vp4-8nJAchWYJ#H8`hPeDUNYr9$XIxdoZVcx(0V`Iax zZlULk5g=5c`IDtPZkT^gG}^6f0=Yd8YyaKA_vMiCRV<)%5a;-KoVj%~b`mgYkX4{! zRy}qpfPKQ_aZa1UX7Ky*W^GU-Ggg;tF8ZgX0-FU>y)YwTA)S^e)8->^F@O-&(|9NbHhNW{tbTYz#}MlychxiPFqJu&&bOd zk!=921UH1FI+|h4f5MS`adC0EDilb|`T5lH2xDX8e_buGb7Ec#jSmeWvvu531Nkq_ z8Ck}_MB0HeTs=_FvX)l$EthJfq~EMU?L)uN^F{UD`3Rc_Fe>9s(?Gd7UHn!6(rdZx zBLjXu$VsDCk2{1m$wqHe*#=k-D0V;&t$N+Sjrfy4;{je5qBEOrkZaMINHpC0dlVcC0f)B4C2nU9T*xDt_KkG4)HSvZcQoQvay69MZ1um((Ea7eM-#l5No@%C$X?^MUd? zJN!Pu4uuL+$FC1WlYuv;=H{d{G>8Bz*j4fpqqo?7_P~@e`Pl!IPhB?ax^h+LqGxVm z(x*^Q5-I{be9(k^{rdIvoT(c6?wtrFHgH)xjH2DGHw)_~7}|IX(B_|RYq$Jt0I&ST3F{plA3q-;vv&aKoG^^9 zEnJXS zd>>nuifIz8duYyqj0_S1=S%=rK$jlaor@-t<9&{jv(hBOnACG}F+Dji3?&9`{P(vs z-a;fF-wHD_Z^vMxhYp#sGn*$jd*8ADqkM2ob-eip2IF$GmOY_9I@jzT*#;)bScU{3pVBIyfh_o+5Yreb+ZtZ&7wda^zRO|qELKB=Gzf7&=#8+83i(F zROMQd=6wve;FRSd|KiNI+FsC4$g$_LxRR-KSn}uM4ke@qOx#<-eqRLVDK8+`3Rk(K zHX)w@*z@_WQ=3K0Ev)jQ{uk9$;G1sDIGS$H^LoRu_#E#slJ+TuqJYLz5w@a-tXN4@3pr#^5d$>a_2MFGtSsnN7bOe zH|vP??nDahUoKz2+dU;eZGj69B`)C#D`k+UJUT5iTwQg{-~_@JQXT@AY?>@QXGT0p z9NX%$=!6&^i?5Z}&~TKO{?b_wTBk2V zp@B^ro3r#&H{lvPz87XCOsHK4IlFim}#CUw{yr}%RCXR85?=#?yD zxT_&R4Q{>><3UA&6wH|)!im6*?PzjE>s;&HBf6HxUGDHPKcKPz-re2ZUDzTTE|U@H z27*#pn|0VK-0?LP6gp>D;-|iOJ85ImI^jZx_fmx2{N6uIx@b?ZCVRX)TaMy%$ZH_P=CI+I8!E6<3UXSuwNJcGnbf4fCxc1Em$lY@4_ z$#;$JIOP;g@AXFJOV?_C1?RNYN%TYgFP&owa?3DrjSMBPY@_L9B;!r-8zwzV7|xSL zB$nM3P z!0vTS31w=7H^_^JMU^wxU+2pP*%gebzGvQf8yaq`3=c<$d~O~>ka~eniOdOaD~yJX zM2)X6$Odmf)F@rXK1-*P$qD1mbo8KGcA`lzKn)7 + + + + + True + 5 + Delete Snapshots + center-on-parent + time-slider-setup + dialog + False + + + + True + + + True + 4 + 2 + 9 + 12 + + + True + True + automatic + automatic + etched-in + + + 300 + True + True + True + True + True + + + + + 2 + 3 + + + + + True + 0 + Select ZFS snapshots to delete from the list below. + + + 2 + GTK_FILL + + + + + + True + 6 + + + True + Type: + + + False + False + 0 + + + + + True + + + + False + False + 1 + + + + + True + File System: + + + False + False + 2 + + + + + True + + + + False + False + 3 + + + + + True + 1 + Name: + + + False + False + 4 + + + + + True + + + + False + False + 5 + + + + + 1 + 2 + GTK_FILL + + + + + + 6 + True + + + + + + 3 + 4 + + + + + + True + 12 + 12 + + + True + 24 + + + True + 6 + True + + + gtk-select-all + True + True + True + True + + + + False + False + 0 + + + + + True + True + True + + + + True + + + True + gtk-clear + + + 0 + + + + + True + D_eselect All + True + True + deselectbutton + + + 1 + + + + + + + 1 + + + + + False + False + 0 + + + + + gtk-delete + True + True + True + True + + + + False + False + 1 + + + + + + + 1 + 2 + 2 + 3 + + + + + + + + + + + + 1 + + + + + True + end + + + + + + gtk-close + True + True + True + True + + + + False + False + 1 + + + + + False + end + 0 + + + + + + + 300 + 5 + Delete Snapshots + True + center-on-parent + True + time-slider-setup + dialog + time-slider-delete + False + + + True + 2 + + + True + 0 + none + + + True + 12 + + + True + 12 + + + True + + + True + 0 + %d snapshots will be deleted. + + + False + 0 + + + + + True + 0 + Press Delete to continue. + + + False + False + 1 + + + + + False + False + 0 + + + + + Exit when done. + True + False + True + + + False + False + 1 + + + + + + + + + True + <b>Summary</b> + True + + + label_item + + + + + False + False + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + + False + False + 0 + + + + + gtk-delete + True + True + True + True + + + + False + False + 1 + + + + + False + end + 0 + + + + + + + 300 + 5 + Deleting Snapshots + True + center-on-parent + time-slider-setup + dialog + False + False + + + True + 2 + + + True + 6 + + + True + 0 + Deleting snapshots... + + + False + False + 0 + + + + + True + True + True + + + False + False + 1 + + + + + False + False + 1 + + + + + True + end + + + + + + + + + False + end + 0 + + + + + + + 5 + False + True + center-on-parent + time-slider-setup + dialog + False + False + + + True + 2 + + + True + 6 + + + True + 0 + Scanning snapshots... + + + False + False + 0 + + + + + True + True + + + False + False + 1 + + + + + 1 + + + + + True + end + + + + + + + + + False + end + 0 + + + + + + diff --git a/usr/share/time-slider/glade/time-slider-setup.glade b/usr/share/time-slider/glade/time-slider-setup.glade new file mode 100644 index 0000000..6117c85 --- /dev/null +++ b/usr/share/time-slider/glade/time-slider-setup.glade @@ -0,0 +1,531 @@ + + + + + + True + 5 + Time Slider Manager + center-on-parent + time-slider-setup + dialog + + + + True + vertical + 12 + + + True + vertical + 18 + + + True + vertical + + + True + True + False + True + + + + True + <b>Enable Time Slider</b> + True + + + + + False + False + 0 + + + + + True + 0 + Time Slider backs up data regularly by taking timed ZFS Snapshots + + + False + False + 1 + + + + + False + False + 0 + + + + + True + True + True + + + True + vertical + 12 + + + True + 0 + none + + + True + 12 + + + True + vertical + + + Replicate backups to an external drive + True + True + False + True + + + + False + False + 0 + + + + + True + 6 + + + True + 0 + Backup Device: + + + False + False + 0 + + + + + True + + + + 1 + + + + + False + 1 + + + + + + + + + True + <b>External Back Up</b> + True + + + label_item + + + + + False + False + 0 + + + + + True + 0 + none + + + True + 6 + 12 + + + True + vertical + 12 + + + True + 2 + 2 + 72 + + + True + 0 + For advanced users + + + 1 + 2 + 1 + 2 + + + + + True + 0 + Recommended for most users + + + 1 + 2 + + + + + C_ustom: + True + True + False + True + True + defaultfsradio + + + + 1 + 2 + GTK_FILL + + + + + _All + True + True + False + True + True + True + + + + GTK_FILL + + + + + False + False + 0 + + + + + True + True + automatic + automatic + + + True + True + True + + + + + 1 + + + + + True + vertical + 5 + + + True + 0 + Reduce backups when storage space usage exceeds: + + + False + False + 0 + + + + + True + 5 + + + True + True + + 0 0 100 1 10 0 + True + if-valid + + + False + False + 0 + + + + + True + 0 + % + + + False + False + 1 + + + + + True + 0 + of file system capacity + + + 2 + + + + + False + False + 1 + + + + + False + False + 2 + + + + + + + + + True + <b>File Systems To Back Up</b> + True + + + label_item + + + + + 1 + + + + + + + True + Advanced Options + True + + + label_item + + + + + 1 + + + + + 0 + + + + + True + 6 + 18 + + + True + True + True + + + + True + 5 + + + True + gtk-delete + + + 0 + + + + + True + _Delete Snapshots... + True + True + + + 1 + + + + + + + False + 0 + + + + + True + 6 + end + + + gtk-cancel + True + True + True + True + + + + False + False + 0 + + + + + gtk-ok + True + True + True + True + + + + False + False + 1 + + + + + 1 + + + + + False + False + 1 + + + + + + + 5 + False + True + center-on-parent + time-slider-setup + dialog + False + False + + + True + 2 + + + True + vertical + 6 + + + True + 0 + Configuring Time Slider... + + + False + False + 0 + + + + + True + True + + + False + False + 1 + + + + + 1 + + + + + True + end + + + + + + + + + False + end + 0 + + + + + + diff --git a/usr/share/time-slider/glade/time-slider-snapshot.glade b/usr/share/time-slider/glade/time-slider-snapshot.glade new file mode 100644 index 0000000..3e883f7 --- /dev/null +++ b/usr/share/time-slider/glade/time-slider-snapshot.glade @@ -0,0 +1,133 @@ + + + + + + 400 + 5 + Take a snapshot now + True + GTK_WIN_POS_CENTER_ON_PARENT + True + time-slider-setup + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + + True + 2 + + + True + 2 + + + True + gtk-dialog-question + 6 + + + + + True + 3 + 2 + + + + + + True + True + + + 2 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + + + + + True + 0 + Take a snapshot of + + + GTK_FILL + GTK_FILL + + + + + True + 0 + with the name : + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + 1 + + + + + 3 + 1 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-cancel + True + 0 + + + + + + True + True + True + gtk-ok + True + 0 + + + + 1 + + + + + False + GTK_PACK_END + + + + + + diff --git a/usr/share/time-slider/glade/time-slider-version.glade b/usr/share/time-slider/glade/time-slider-version.glade new file mode 100644 index 0000000..f59fe47 --- /dev/null +++ b/usr/share/time-slider/glade/time-slider-version.glade @@ -0,0 +1,305 @@ + + + + + + 500 + 400 + True + 5 + Time Slider File Version Explorer + GTK_WIN_POS_CENTER_ON_PARENT + time-slider-setup + + + + True + 12 + + + True + + + True + 0 + + + True + 5 + 5 + 5 + + + True + + + True + True + True + Open the current version of the file + GTK_RELIEF_NONE + 0 + + + + True + 24 + gtk-file + 6 + + + + + False + False + 5 + + + + + True + 3 + 2 + + + True + 0 + last modified : + + + 2 + 3 + + + + + True + 0 + 19/01/09 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + size : + + + 1 + 2 + + + + + True + 0 + 10 Mo + + + 1 + 2 + 1 + 2 + + + + + True + 0 + filename + + + 1 + 2 + + + + + True + 0 + name : + + + + + False + False + 1 + + + + + + + + + True + <b>Current Version</b> + True + + + label_item + + + + + False + False + 4 + + + + + True + 0 + + + True + 5 + 5 + 5 + 5 + + + True + + + True + True + Scanning for older versions + + + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_ETCHED_IN + + + True + True + Double click to open the file + True + True + 0 + True + + + + + + + 1 + + + + + + + + + True + <b>Older Versions</b> + True + + + label_item + + + + + 1 + + + + + + + True + 6 + 18 + + + True + False + True + True + True + Compare the current version and the selected older version + 0 + + + + True + + + True + gtk-print-preview + + + + + True + compare + + + 1 + + + + + + + False + False + + + + + True + 6 + GTK_BUTTONBOX_END + + + + + + True + True + True + gtk-close + True + 0 + + + + 1 + + + + + 1 + + + + + False + False + 1 + + + + + + diff --git a/usr/share/time-slider/lib/plugin/__init__.py b/usr/share/time-slider/lib/plugin/__init__.py new file mode 100644 index 0000000..649d6ed --- /dev/null +++ b/usr/share/time-slider/lib/plugin/__init__.py @@ -0,0 +1,25 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir)) + diff --git a/usr/share/time-slider/lib/plugin/plugin.py b/usr/share/time-slider/lib/plugin/plugin.py new file mode 100644 index 0000000..1b820ff --- /dev/null +++ b/usr/share/time-slider/lib/plugin/plugin.py @@ -0,0 +1,167 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import sys +import subprocess +import pluginsmf + +from time_slider import smf, autosnapsmf, util + +PLUGINBASEFMRI = "svc:/application/time-slider/plugin" + + +class Plugin(Exception): + + def __init__(self, instanceName, debug=False): + self.verbose = debug + util.debug("Instantiating plugin for:\t%s" % (instanceName), self.verbose) + self.smfInst = pluginsmf.PluginSMF(instanceName) + self._proc = None + + # Note that the associated plugin service's start method checks + # that the command is defined and executable. But SMF doesn't + # bother to do this for offline services until all dependencies + # (ie. time-slider) are brought online. + # So we also check the permissions here. + command = self.smfInst.get_trigger_command() + try: + statinfo = os.stat(command) + other_x = (statinfo.st_mode & 01) + if other_x == 0: + raise RuntimeError, 'Plugin: %s:\nConfigured trigger command is not ' \ + 'executable:\n%s' \ + % (self.smfInst.instanceName, command) + except OSError: + raise RuntimeError, 'Plugin: %s:\nCan not access the configured ' \ + 'plugin/trigger_command:\n%s' \ + % (self.smfInst.instanceName, command) + + + def execute(self, schedule, label): + + triggers = self.smfInst.get_trigger_list() + try: + triggers.index("all") + except ValueError: + try: + triggers.index(schedule) + except ValueError: + return + + # Skip if already running + if self.is_running() == True: + util.debug("Plugin: %s is already running. Skipping execution" \ + % (self.smfInst.instanceName), \ + self.verbose) + return + # Skip if plugin FMRI has been disabled or placed into maintenance + cmd = [smf.SVCSCMD, "-H", "-o", "state", self.smfInst.instanceName] + outdata,errdata = util.run_command(cmd) + state = outdata.strip() + if state == "disabled" or state == "maintenance": + util.debug("Plugin: %s is in %s state. Skipping execution" \ + % (self.smfInst.instanceName, state), \ + self.verbose) + return + + cmd = self.smfInst.get_trigger_command() + util.debug("Executing plugin command: %s" % str(cmd), self.verbose) + svcFmri = "%s:%s" % (autosnapsmf.BASESVC, schedule) + + os.putenv("AUTOSNAP_FMRI", svcFmri) + os.putenv("AUTOSNAP_LABEL", label) + try: + os.putenv("PLUGIN_FMRI", self.smfInst.instanceName) + self._proc = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + self._proc = None + + def is_running(self): + if self._proc == None: + util.debug("Plugin child process is not started", self.verbose) + return False + else: + self._proc.poll() + if self._proc.returncode == None: + util.debug("Plugin child process is still running", + self.verbose) + return True + else: + util.debug("Plugin child process has ended", self.verbose) + return False + + +class PluginManager(): + + def __init__(self, debug=False): + self.plugins = [] + self.verbose = debug + + def execute_plugins(self, schedule, label): + util.debug("Executing plugins for \"%s\" with label: \"%s\"" \ + % (schedule, label), \ + self.verbose) + for plugin in self.plugins: + plugin.execute(schedule, label) + + + def refresh(self): + self.plugins = [] + cmd = [smf.SVCSCMD, "-H", "-o", "state,FMRI", PLUGINBASEFMRI] + + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + if err != 0: + self._refreshLock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for line in outdata.rstrip().split('\n'): + line = line.rstrip().split() + state = line[0] + fmri = line[1] + + # Note that the plugins, being dependent on the time-slider service + # themselves will typically be in an offline state when enabled. They will + # transition to an "online" state once time-slider itself comes + # "online" to satisfy it's dependency + if state == "online" or state == "offline" or state == "degraded": + util.debug("Found enabled plugin:\t%s" % (fmri), self.verbose) + try: + plugin = Plugin(fmri, self.verbose) + self.plugins.append(plugin) + except RuntimeError, message: + sys.stderr.write("Ignoring misconfigured plugin: %s\n" \ + % (fmri)) + sys.stderr.write("Reason:\n%s\n" % (message)) + else: + util.debug("Found disabled plugin:\t%s" + fmri, self.verbose) + diff --git a/usr/share/time-slider/lib/plugin/pluginsmf.py b/usr/share/time-slider/lib/plugin/pluginsmf.py new file mode 100644 index 0000000..c71df82 --- /dev/null +++ b/usr/share/time-slider/lib/plugin/pluginsmf.py @@ -0,0 +1,66 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import sys +import subprocess +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir)) +from time_slider import smf, autosnapsmf, util + + +PLUGINBASEFMRI = "svc:/application/time-slider/plugin" +PLUGINPROPGROUP = "plugin" + +class PluginSMF(smf.SMFInstance): + + def __init__(self, instanceName): + smf.SMFInstance.__init__(self, instanceName) + self.triggerCommand = None + self.triggers = None + + def get_trigger_command(self): + # FIXME Use mutex locking for MT safety + if self.triggerCommand == None: + value = self.get_prop(PLUGINPROPGROUP, "trigger_command") + self.triggerCommand = value.strip() + return self.triggerCommand + + def get_trigger_list(self): + #FIXME Use mutex locking to make MT-safe + if self.triggers == None: + self.triggers = [] + value = self.get_prop(PLUGINPROPGROUP, "trigger_on") + + # Strip out '\' characters inserted by svcprop + triggerList = value.strip().replace('\\', '').split(',') + for trigger in triggerList: + self.triggers.append(trigger.strip()) + return self.triggers + + def get_verbose(self): + value = self.get_prop(PLUGINPROPGROUP, "verbose") + if value == "true": + return True + else: + return False + diff --git a/usr/share/time-slider/lib/plugin/pluginsmf.py~ b/usr/share/time-slider/lib/plugin/pluginsmf.py~ new file mode 100644 index 0000000..e6c44df --- /dev/null +++ b/usr/share/time-slider/lib/plugin/pluginsmf.py~ @@ -0,0 +1,67 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import sys +import subprocess +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir)) +print join (dirname(__file__), pardir) +from time_slider import smf, autosnapsmf, util + + +PLUGINBASEFMRI = "svc:/application/time-slider/plugin" +PLUGINPROPGROUP = "plugin" + +class PluginSMF(smf.SMFInstance): + + def __init__(self, instanceName): + smf.SMFInstance.__init__(self, instanceName) + self.triggerCommand = None + self.triggers = None + + def get_trigger_command(self): + # FIXME Use mutex locking for MT safety + if self.triggerCommand == None: + value = self.get_prop(PLUGINPROPGROUP, "trigger_command") + self.triggerCommand = value.strip() + return self.triggerCommand + + def get_trigger_list(self): + #FIXME Use mutex locking to make MT-safe + if self.triggers == None: + self.triggers = [] + value = self.get_prop(PLUGINPROPGROUP, "trigger_on") + + # Strip out '\' characters inserted by svcprop + triggerList = value.strip().replace('\\', '').split(',') + for trigger in triggerList: + self.triggers.append(trigger.strip()) + return self.triggers + + def get_verbose(self): + value = self.get_prop(PLUGINPROPGROUP, "verbose") + if value == "true": + return True + else: + return False + diff --git a/usr/share/time-slider/lib/plugin/rsync/__init__.py b/usr/share/time-slider/lib/plugin/rsync/__init__.py new file mode 100644 index 0000000..a2667b2 --- /dev/null +++ b/usr/share/time-slider/lib/plugin/rsync/__init__.py @@ -0,0 +1,25 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, pardir)) + diff --git a/usr/share/time-slider/lib/plugin/rsync/backup.py b/usr/share/time-slider/lib/plugin/rsync/backup.py new file mode 100644 index 0000000..ce44090 --- /dev/null +++ b/usr/share/time-slider/lib/plugin/rsync/backup.py @@ -0,0 +1,1260 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import os.path +import fcntl +import tempfile +import sys +import subprocess +import statvfs +import time +import threading +import math +import syslog +import gobject +import gio +import dbus +import shutil +import copy +from bisect import insort, bisect_left + +from time_slider import util, zfs, dbussvc, autosnapsmf, timeslidersmf +import rsyncsmf + + +# Set to True if SMF property value of "plugin/command" is "true" +verboseprop = "plugin/verbose" +propbasename = "org.opensolaris:time-slider-plugin" + +class RsyncError(Exception): + """Generic base class for RsyncError + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + self.msg = msg + def __str__(self): + return repr(self.msg) + + +class RsyncTargetDisconnectedError(RsyncError): + """Exception raised when the backup device goes offline during + the rsync transfer. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, source, dest, message): + msg = "Target directory error during rsync backup from " \ + "%s to target \'%s\' Rsync error details:\n%s" \ + % (source, dest, message) + RsyncError.__init__(self, msg) + + +class RsyncTransferInterruptedError(RsyncError): + """Exception raised when the rsync transfer process pid was + interrupted or killed during the rsync transfer. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, source, dest, message): + msg = "Interrputed rsync transfer from %s to %s " \ + "Rsync error details:\n%s" % (source, dest, message) + RsyncError.__init__(self, msg) + + +class RsyncSourceVanishedError(RsyncError): + """Exception raised when rsync could only partially transfer + due to the contents of the source directory being removed. + Possibly due to a snapshot being destroyed during transfer + because of immediate or deferred (holds released) destruction. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, source, dest, message): + msg = "Rsync source directory vanished during transfer of %s to %s" \ + "Rsync error details:\n%s" % (source, dest, message) + RsyncError.__init__(self, msg) + + +class RsyncProcess(threading.Thread): + + + def __init__(self, source, target, latest=None, verbose=False, logfile=None): + + self._sourceDir = source + self._backupDir = target + self._latest = latest + self._verbose = verbose + self._proc = None + self._forkError = None + self._logFile = logfile + # Init done. Now initiaslise threading. + threading.Thread.__init__ (self) + + def run(self): + try: + self._proc = subprocess.Popen(self._cmd, + stderr=subprocess.PIPE, + close_fds=True) + except OSError as e: + # _check_exit_code() will pick up this and raise an + # exception in the original thread. + self._forkError = "%s: %s" % (self._cmd[0], str(e)) + else: + self._stdout,self._stderr = self._proc.communicate() + self._exitValue = self._proc.wait() + + def _check_exit_code(self): + if self._forkError: + # The rsync process failed to execute, probably + # received an OSError exception. Pass it up. + raise RsyncError(self._forkError) + + if self._exitValue == 0: + return + # Non zero return code means rsync encountered an + # error which may be transient or require sys-admin + # intervention to fix. + + # This method basically just maps known rsync exit codes + # to exception classes. + + # Rsync exit value codes (non-zero) + + # 11/12 Indicates backup drive was disconnected during + # transfer. Recoverable once drive reconnected: + # 11 Error in file I/O + # 12 Error in rsync protocol data stream + if self._exitValue == 11 or \ + self._exitValue == 12: + raise RsyncTargetDisconnectedError(self._sourceDir, + self._backupDir, + self._stderr) + # Transfer pid interrupted by SIGUSR1 or SIGINT. Recoverable: + # 20 Received SIGUSR1 or SIGINT + elif self._proc.returncode == 20: + raise RsyncTransferInterruptedError(self._sourceDir, + self._backupDir, + self._stderr) + + # For everything else unknown or unexpected, treat it as + # fatal and provide the rsync stderr output. + else: + raise RsyncError(self._stderr) + + def start_backup(self): + # First, check to see if the rsync destination + # directory is accessible. + try: + os.stat(self._backupDir) + except OSError: + util.debug("Backup directory is not " \ + "currently accessible: %s" \ + % (self._backupDir), + self._verbose) + #FIXME exit/exception needs to be raise here + # or status needs to be set. + return + + try: + os.stat(self._sourceDir) + except OSError: + util.debug("Backup source directory is not " \ + "currently accessible: %s" \ + % (self._sourceDir), + self._verbose) + #FIXME exit/excpetion needs to be raise here + # or status needs to be set. + return + + if self._latest: + self._cmd = ["/usr/bin/rsync", "-a", "--inplace",\ + "%s/." % (self._sourceDir), \ + "--link-dest=%s" % (self._latest), \ + self._backupDir] + else: + self._cmd = ["/usr/bin/rsync", "-a", "--inplace",\ + "%s/." % (self._sourceDir), \ + self._backupDir] + + if self._logFile: + self._cmd.insert(1, "--log-file=%s" % (self._logFile)) + if self._verbose: + self._cmd.insert(1, "-vv") + + self.start() + + +class BackupQueue(): + + def __init__(self, fmri, dbus, mainLoop=None): + self._bus = dbus + self._mainLoop = mainLoop + self._started = False + self._pluginFMRI = fmri + self._smfInst = rsyncsmf.RsyncSMF(self._pluginFMRI) + self._verbose = self._smfInst.get_verbose() + self._rsyncVerbose = self._smfInst.get_rsync_verbose() + self._propName = "%s:%s" % (propbasename, fmri.rsplit(':', 1)[1]) + + # Variables to quickly access time sorted backups and + # to map directory names to mtimes. + self._backupDirs = None + self._backups = None + self._backupTimes = {} + + released = release_held_snapshots(self._propName) + for snapName in released: + util.debug("Released dangling userref on: " + snapName, + self._verbose) + + self._tempSchedules = None + # List of all snapshots currently in our backup queue + self._pendingList = list_pending_snapshots(self._propName) + + # Try to backup in sets of snapshots grouped by a common + # snapshot label. These get taken from the head of the + # pending list. After a working set has been completed, + # pending list gets refreshed and a new working set is + # extracted. + self._currentQueueSet = [] + self._skipList = [] + self._queueLength = 0 + + self._cleanupThreshold = self._smfInst.get_cleanup_threshold() + if self._cleanupThreshold < 1 or \ + self._cleanupThreshold > 99: + # Stupid value. #FIXME Plugin should be checked by SMF start + # method and placed into maintenance if this happens. + # For now, go with default + util.log_error(syslog.LOG_ERR, + "Invalid value for SMF property " \ + ": %d." \ + "Using default value of 95%" \ + % (self._cleanupThreshold)) + + # Base variables for backup device. Will be initialised + # later in _find_backup_device() + self._smfTargetKey = self._smfInst.get_target_key() + sys,self._nodeName,rel,ver,arch = os.uname() + self._rsyncBaseDir = None + self._rsyncDir = None + self._keyFile = None + + tsSMF = timeslidersmf.TimeSliderSMF() + self._labelSeparator = tsSMF.get_separator() + del tsSMF + # Finally go look for the backup device + self._find_backup_device() + + def empty_trash_folders(self): + trashDirs = [] + trashedBackups = [] + os.chdir(self._rsyncDir) + for root, dirs, files in os.walk(self._rsyncDir): + if '.time-slider' in dirs: + dirs.remove ('.time-slider') + trashDir = os.path.join(root, + rsyncsmf.RSYNCTRASHSUFFIX) + if os.path.exists(trashDir): + trashDirs.append(trashDir) + for trashDir in trashDirs: + os.chdir(trashDir) + trashItems = [] + trashItems = [d for d in os.listdir(trashDir) \ + if os.path.isdir(d) and + not os.path.islink(d)] + if len(trashItems) > 0: + util.debug("Deleting trash backups in %s" % (trashDir), + self._verbose) + for trashItem in trashItems: + util.debug("Deleting trash item: %s" % (trashItem), + self._verbose) + # FIXME add some dbus notification here to let the + # applet know what's going on. + shutil.rmtree(trashItem) + + def _get_temp_schedules(self): + # Get retention rule for non archival snapshots as per + # rules defined in: + # svc://system/filesystem/zfs/auto-snapshot: + archived = self._smfInst.get_archived_schedules() + triggers = self._smfInst.get_trigger_list() + defScheds = autosnapsmf.get_default_schedules() + customScheds = autosnapsmf.get_custom_schedules() + try: + triggers.index('all') + # Expand the wildcard value 'all' + triggers = [sched for sched,i,p,k in defScheds] + customTriggers = [sched for sched,i,p,k in customScheds] + triggers.extend(customTriggers) + except ValueError: + pass + + self._tempSchedules = [schedule for schedule in defScheds if \ + schedule[0] not in archived] + self._tempSchedules.extend([schedule for schedule in customScheds if \ + schedule[0] not in archived]) + + def _remove_dead_backups(self): + """ + Identifies and removes partially completed backups whose origin + snapshot is no longer in the pending queue, indicating that the + backup will never get completed, in which case it's just a waste + of space + """ + backupDirs = [] + partialDirs = [] + deadBackups = [] + os.chdir(self._rsyncDir) + for root, dirs, files in os.walk(self._rsyncDir): + if '.time-slider' in dirs: + dirs.remove ('.time-slider') + partialDir = os.path.join(root, + rsyncsmf.RSYNCPARTIALSUFFIX) + partialDirs.append(partialDir) + for dirName in partialDirs: + if not os.path.exists(partialDir): + continue + os.chdir(dirName) + partials = [d for d in os.listdir(dirName) \ + if os.path.isdir(d)] + if len(partials) == 0: + continue + suffix = rsyncsmf.RSYNCPARTIALSUFFIX + prefix = self._rsyncDir + # Reconstruct the origin ZFS filesystem name + baseName = dirName.replace(prefix, '', 1).lstrip('/') + fsName = baseName.replace(suffix, '', 1).rstrip('/') + for snapshotLabel in partials: + pending = False + # Reconstruct the origin snapshot name and see + # if it's still pending rsync backup. If it is + # then leave it alone since it can be used to + # resume a partial backup later. Otherwise it's + # never going to be backed up and needs to be + # manually deleted. + snapshotName = "%s@%s" % (fsName, snapshotLabel) + for ctime,name in self._pendingList: + if name == snapshotName: + pending = True + continue + if pending == False: + util.debug("Deleting zombied partial backup: %s" \ + % (os.path.abspath(snapshotLabel)), + self._verbose) + shutil.rmtree(snapshotLabel) + # Don't forget the log file too. + logFile = os.path.join(os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOGSUFFIX, + snapshotLabel + ".log") + try: + os.stat(logFile) + util.debug("Deleting zombie log file: %s" \ + % (os.path.abspath(logFile)), + self._verbose) + os.unlink(logFile) + except OSError: + util.debug("Expected rsync log file not " \ + "found: %s"\ + % (os.path.abspath(logFile)), + self._verbose) + + def _discover_backups(self): + self._backupDirs = [] + self._backups = [] + self._backupTimes = {} + # backupTimes dictionary is not primarily used + # to store directory listings, but to map + # mtimes to directories for quick lookups. + + os.chdir (self._rsyncDir) + for root, dirs, files in os.walk(self._rsyncDir): + if '.time-slider' in dirs: + dirs.remove('.time-slider') + backupDir = os.path.join(root, rsyncsmf.RSYNCDIRSUFFIX) + if os.path.exists(backupDir): + insort(self._backupDirs, os.path.abspath(backupDir)) + for dirName in self._backupDirs: + self._backupTimes[dirName] = {} + os.chdir(dirName) + dirList = [d for d in os.listdir(dirName) \ + if os.path.isdir(d) and + not os.path.islink(d)] + for d in dirList: + mtime = os.stat(d).st_mtime + insort(self._backups, [long(mtime), os.path.abspath(d)]) + self._backupTimes[dirName][d] = mtime + + def _find_backup_device(self): + # Determine the rsync backup dir. This is the target dir + # defined by the SMF instance plus the "TIMESLIDER/" + # suffix. Try finding it at the preconfigured path first, + # then failing that, scan removable media mounts, in case it + # got remounted under a different path than at setup time. + self._rsyncBaseDir = None + path = self._smfInst.get_target_dir() + if self._validate_rsync_target(path) == True: + self._rsyncBaseDir = path + util.debug("Backup target device online: %s" % (path), + self._verbose) + else: + util.debug("Backup target device not mounted at: %s" \ + "Scanning removable devices.." \ + % (path), + self._verbose) + volMonitor = gio.volume_monitor_get() + mounts = volMonitor.get_mounts() + for mount in mounts: + root = mount.get_root() + path = root.get_path() + if self._validate_rsync_target(path) == True: + util.debug("Located backup target device at: %s" \ + % (path), + self._verbose) + self._rsyncBaseDir = path + + if self._rsyncBaseDir != None: + self._rsyncDir = os.path.join(self._rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + self._keyFile = os.path.join(self._rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + + + def _validate_rsync_target(self, path): + """ + Tests path to see if it is the pre-configured + rsync backup device path. + Returns True on success, otherwise False + """ + # FIXME - this is duplicate in the applet and should + # be moved into a shared module + if not os.path.exists(path): + return False + testDir = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + testKeyFile = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + if os.path.exists(testDir) and \ + os.path.exists(testKeyFile): + testKeyVal = None + f = open(testKeyFile, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetKey = val.strip() + break + f.close() + if targetKey == self._smfTargetKey: + return True + return False + + def _find_deleteable_backups(self, timestamp): + """ + Returns a list of backup directory paths that are older than + timestamp, and can be deleted in order to make room for newer + backups. Certain restrictions apply such as: + - The backup cannot be locked (indicating it's being or about + to be deleted by another process + - The last remaining backup in it's parent directory, which + would mean it is the only backup of a given fileystem on the + device. We always keep at least 1 full backup of a filesystem + on the device if it belongs to a currently selected filesystem. + """ + deleteables = [] + # This should have already occured in + # backup_snapshot() mainloop method + if self._backupDirs == None: + self._discover_backups() + + if len(self._backupDirs) == 0: + # We were not able to find any backups to delete. Try again later + return [] + + # Extract the subset of self._backupDirs that are older than timestamp + idx = bisect_left(self._backups, [timestamp, '']) + subset = self._backups[0:idx] + + # Copy the backupTimes dictionary since so we can + # modify it safely. + copiedTimes = copy.deepcopy(self._backupTimes) + for mtime, dirName in subset: + head,tail = os.path.split(dirName) + if len(copiedTimes[head]) < 2: + # We can only delete this single backup provided + # it's filesystem is no longer tagged for rsync + # replication. Othewise we need to leave at least + # one backup on the device at all times, and also + # to provide an incremental backup point for + # future backups. + snapName = backup_name_to_snapshot_name(dirName) + snapshot = zfs.Snapshot(snapName) + fs = zfs.Filesystem(snapshot.fsname) + if fs.get_user_property(rsyncsmf.RSYNCFSTAG) == "true": + continue + else: + # If we are going to actually remove this final + # remaining backup of this filesystem then we need + # to unqueue any pending snapshots of it, + # otherwise after deleting this one, we'll just + # start backing up the older snapshots of this + # filesystem afterwards, which is a waste of time + # and space. + pending = [name for time,name in \ + list_pending_snapshots(self._propName) if \ + name.find(snapshot.fsname + '@') == 0] + cmd = [zfs.PFCMD, zfs.ZFSCMD, "inherit", self._propName] + util.debug("Unqueuing pending backups of deselected " \ + "filesystem: " + snapshot.fsname + '\n' + \ + str(pending), + self._verbose) + cmd.extend(pending) + util.run_command(cmd) + + lockFileDir = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOCKSUFFIX) + lockFile = os.path.join(lockFileDir, tail) + + if not os.path.exists(lockFile): + # No lock file so we are free to delete it. + deleteables.append([mtime, dirName]) + # Remove it from copiedTimes so it doesn't get + # double counted + del copiedTimes[head][tail] + continue + # Lock file so probably can't delete this, but try it out + # anyway incase it's stale/unlocked + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't delete backup %s because it " \ + "is locked by another process." \ + "Skipping" % (dirName), + self._verbose) + continue + # Ok, we can still delete it, but get rid of the stale lock file + lockFp.close() + os.unlink(lockFile) + deleteabled.append([mtime, dirName]) + # Remove it from copiedTimes so it doesn't get + # double counted + del copiedTimes[head][tail] + return deleteables + + def _recover_space(self, deleteables): + + # Important: + # Don't actually loop throught this list fully. Break out + # as soon as pool capacity is beneath the threshhold level + # again. + remainder = deleteables[:] + for mtime,dirName in deleteables: + if util.get_filesystem_capacity(self._rsyncDir) < \ + self._cleanupThreshold: + # No need to delete anything further + return remainder + lockFile = None + lockFp = None + head,tail = os.path.split(dirName) + + # Check if it has a lock file and try to grab it. + # If that fails we need to move onto the next backup in + # the list. + lockFileDir = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOCKSUFFIX) + lockFile = os.path.join(lockFileDir, tail) + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't delete backup %s because it " \ + "is locked by another process." \ + "Skipping" % (dirName), + self._verbose) + # Remove if from the remainder list + idx = bisect_left(remainder, [mtime, dirName]) + del remainder[idx] + continue + + trash = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCTRASHSUFFIX) + trashDir = os.path.join(trash, tail) + + if not os.path.exists(trash): + os.makedirs(trash, 0755) + + util.debug("Deleting rsync backup to recover space: %s"\ + % (dirName), self._verbose) + os.rename(dirName, trashDir) + lockFp.close() + os.unlink(lockFile) + shutil.rmtree(trashDir) + # Remove the log file if it exists + logFile = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOGSUFFIX, + tail + ".log") + if os.path.exists(logFile): + os.unlink(logFile) + else: + util.debug("Expected to find log file %s when deleting %s " \ + "during space recovery" % (logFile, dirName), + self._verbose) + # Remove dirName from the backup list + idx = bisect_left(self._backups, [mtime, dirName]) + del self._backups[idx] + # Remove if from the remainder list too + idx = bisect_left(remainder, [mtime, dirName]) + del remainder[idx] + return remainder + + def backup_snapshot(self): + # First, check to see if the rsync destination + # directory is accessible. + + if self._rsyncBaseDir == None: + util.debug("Backup target device is not " \ + "accessible right now: %s" \ + % (self._smfInst.get_target_dir()), + self._verbose) + self._bus.rsync_unsynced(len(self._pendingList)) + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + + # Extra paranoia + if self._validate_rsync_target(self._rsyncBaseDir) == False: + util.debug("Backup target directory does not " \ + "have a matching configuration key. " \ + "Possibly old or wrong device: %s" \ + % (self._keyFile), + self._verbose) + self._bus.rsync_unsynced(len(self._pendingList)) + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + + # Before getting started. Identify what needs to be cleaned up on + # the backup target. + if self._tempSchedules == None: + self._get_temp_schedules() + + # Remove incompleteable partial backups, then find out what + # complete backups we already have on the target device + if self._started == False: + self._remove_dead_backups() + self._discover_backups() + + if len(self._currentQueueSet) == 0: + # Means we are just getting started or have just completed + # backup of one full set of snapshots. Clear out anything + # we may have moved to the trash during the previous + # backup set iterations. + self.empty_trash_folders() + # Refresh the pending list and build a new working set queue + self._pendingList = list_pending_snapshots(self._propName) + # Remove skipped items to avoid infinite looping. + for item in self._skipList: + try: + self._pendingList.remove(item) + except ValueError: + pass + if len(self._pendingList) == 0: + # If something was actually backed up, signal + # that it is now completed. + if self._started == True: + self._bus.rsync_complete(self._rsyncBaseDir) + self._bus.rsync_synced() + # Nothing to do exit + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + else: + # Construct a new working set queue + # Identify the newest snapshot and then all + # snapshots with a matching snapshot label + self._queueLength = len(self._pendingList) + ctime,headSnapName = self._pendingList[0] + label = headSnapName.rsplit("@", 1)[1] + self._currentQueueSet = \ + [(ctime,snapName) for \ + ctime,snapName in self._pendingList if \ + snapName.rsplit("@", 1)[1] == label] + + + if len(self._backups) > 0: + oldestBackupTime, oldestBackup = self._backups[0] + qTime, qItem = self._currentQueueSet[0] + + # If the backup device is nearly full, don't + # bother trying to backup anything unless it's + # newer than the oldest backup set already on + # the device. + # This avoids potential situations where if the + # backup device has severely limited capacity and + # older backups sets were in the queue, newer backups + # might get deleted in order to make room for older + # ones, creating a downward spiral. + capacity = util.get_filesystem_capacity(self._rsyncDir) + if capacity > self._cleanupThreshold: + # Find backups older than qTime that could in theory + # be deleted in order to make room for the curtent + # pending item. + deleteables = self._find_deleteable_backups(qTime) + + if len(deleteables) == 0 and \ + qTime < oldestBackupTime: + util.debug("%s has exceeded %d%% of its capacity. " \ + "Skipping pending backups prior to: " \ + "%s" % (self._rsyncDir, + self._cleanupThreshold, + time.ctime(oldestBackupTime)), + self._verbose) + if self._started == True: + self._bus.rsync_complete(self._rsyncBaseDir) + self._bus.rsync_synced() + # Nothing to do exit + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + + if self._started == False: + self._started = True + self._bus.rsync_started(self._rsyncBaseDir) + + ctime,snapName = self._currentQueueSet[0] + snapshot = zfs.Snapshot(snapName, long(ctime)) + # Make sure the snapshot didn't get destroyed since we last + # checked it. + remainingList = self._currentQueueSet[1:] + if snapshot.exists() == False: + util.debug("Snapshot: %s no longer exists. Skipping" \ + % (snapName), self._verbose) + self._currentQueueSet = remainingList + return True + + # Place a hold on the snapshot so it doesn't go anywhere + # while rsync is trying to back it up. + snapshot.hold(self._propName) + self._queueLength -= 1 + fs = zfs.Filesystem(snapshot.fsname) + sourceDir = None + if fs.is_mounted() == True: + # Get the mountpoint + mountPoint = fs.get_mountpoint() + sourceDir = "%s/.zfs/snapshot/%s" \ + % (mountPoint, snapshot.snaplabel) + else: + # If the filesystem is not mounted just skip it. If it's + # not mounted then nothing is being written to it. And + # we can just catch up with it again later if it doesn't + # get expired by time-sliderd + util.debug("%s is not mounted. Skipping." \ + % (snapshot.fsname), self._verbose) + snapshot.release(self._propName) + self._skipList.append(ctime, snapName) + self._currentQueueSet = remainingList + return True + + # targetDir is the parent folder of all backups + # for a given filesystem + targetDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCDIRSUFFIX) + # partialDir is a separate directory in which + # snapshots are initially backed up to. Upon successful + # completion they are moved to the backupDir. + partialDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCPARTIALSUFFIX, + snapshot.snaplabel) + lockFileDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCLOCKSUFFIX) + logDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCLOGSUFFIX) + logFile = os.path.join(logDir, + snapshot.snaplabel + ".log") + + + # backupDir is the full directory path where the new + # backup will be located ie / + backupDir = os.path.join(targetDir, snapshot.snaplabel) + + # Figure out the closest previous backup. Since we + # backup newest first instead of oldest first it's + # determined as follows: + # If queued backup item is newer than the most recent + # backup on the backup target, use the most recent + # backup as the incremental source. + # Othewise identify the backup on the device that is + # nearest to but newer than the queued backup. + nearestOlder = None + nearestNewer = None + dirList = [] + + if not os.path.exists(partialDir): + os.makedirs(partialDir, 0755) + if not os.path.exists(logDir): + os.makedirs(logDir, 0755) + + if not os.path.exists(targetDir): + os.makedirs(targetDir, 0755) + # Add the new directory to our internal + # mtime dictionary and sorted list. + self._backupTimes[targetDir] = {} + insort(self._backupDirs, targetDir) + else: + for name,value in self._backupTimes[targetDir].items(): + if ctime > value: + if nearestOlder == None or \ + value > nearestOlder[1]: + nearestOlder = [name, value] + else: + if nearestNewer == None or \ + value < nearestNewer[1]: + nearestNewer = [name, value] + + os.chdir(targetDir) + link = None + linkDest = None + lockFile = None + lockFp = None + if nearestNewer: + link = nearestNewer[0] + elif nearestOlder: + link = nearestOlder[0] + if link: + linkDest = os.path.realpath(link) + # Create a lock for linkDest. We need to ensure that + # nautilus' restore view or the time-slider-delete + # GUI doesn't attempt to delete it or move it to the + # trash while it is being used by rsync for incremental + # backup. + lockFile = os.path.join(lockFileDir, + link + ".lock") + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't perform incremental rsync of %s because " \ + "unable to obtain exclusive lock on incremental " \ + "backup reference point: %s. Exiting" \ + % (lockFile), self._verbose) + os.chdir("/") + snapshot.release(self._propName) + sys.exit(1) + + self._rsyncProc = RsyncProcess(sourceDir, + partialDir, + linkDest, + self._rsyncVerbose, + logFile) + + # Notify the applet of current status via dbus + self._bus.rsync_current(snapshot.name, self._queueLength) + + # Set umask temporarily so that rsync backups are read-only to + # the owner by default. Rync will override this to match the + # permissions of each snapshot as appropriate. + origmask = os.umask(0222) + util.debug("Starting rsync backup of '%s' to: %s" \ + % (sourceDir, partialDir), + self._verbose) + self._rsyncProc.start_backup() + + warningDone = False + while self._rsyncProc.is_alive(): + if len(self._backups) > 0: + # Monitor backup target capacity while we wait for rsync. + capacity = util.get_filesystem_capacity(self._rsyncDir) + if capacity > self._cleanupThreshold: + # Find backups older than qTime that could in theory + # be deleted in order to make room for the curtent + # pending item. + deleteables = self._find_deleteable_backups(qTime) + # Only generate annoying debug message once instead of + # every 5 seconds. + if warningDone == False: + util.debug("Backup device capacity exceeds %d%%. " \ + "Found %d deleteable backups for space " \ + "recovery." \ + % (capacity, len(deleteables)), + self._verbose) + warningDone = True + if len(deleteables) > 0: + deleteables = self._recover_space(deleteables) + time.sleep(5) + + try: + self._rsyncProc._check_exit_code() + except (RsyncTransferInterruptedError, + RsyncTargetDisconnectedError, + RsyncSourceVanishedError) as e: + os.chdir("/") + snapshot.release(self._propName) + util.log_error(syslog.LOG_ERR, str(e)) + # These are recoverable, so exit for now and try again + # later + sys.exit(-1) + + except RsyncError as e: + # If the backup device went offline we need to chdir + # out of it or running further commands might fail. + os.chdir("/") + util.log_error(syslog.LOG_ERR, + "Unexpected rsync error encountered: \n" + \ + str(e)) + util.log_error(syslog.LOG_ERR, + "Rsync log file location: %s" \ + % (os.path.abspath(logFile))) + util.log_error(syslog.LOG_ERR, + "Placing plugin into maintenance mode") + self._smfInst.mark_maintenance() + snapshot.release(self._propName) + sys.exit(-1) + + finally: + if lockFp: + lockFp.close() + os.unlink(lockFile) + + util.debug("Rsync process exited", self._verbose) + os.umask(origmask) + + # Move the completed backup from the partial dir to the + # the propert backup directory + util.debug("Renaming completed backup from %s to %s" \ + % (partialDir, backupDir), self._verbose) + os.rename(partialDir, backupDir) + + # Reset the mtime and atime properties of the backup directory so that + # they match the snapshot creation time. This is extremely important + # because the backup mechanism relies on it to determine backup times + # and nearest matches for incremental rsync (linkDest) + os.utime(backupDir, (long(ctime), long(ctime))) + # Update the dictionary and time sorted list with ctime also + self._backupTimes[targetDir][snapshot.snaplabel] = long(ctime) + insort(self._backups, [long(ctime), os.path.abspath(backupDir)]) + snapshot.set_user_property(self._propName, "completed") + snapshot.release(self._propName) + self._currentQueueSet = remainingList + + # Now is a good time to clean out the directory: + # Check to see if the backup just completed belonged to an + # auto-snapshot schedule and whether older backups should get + # deleted. + if snapshot.snaplabel.find(autosnapsmf.SNAPLABELPREFIX) == 0: + tempSchedule = None + label = None + for schedule in self._tempSchedules: + label = "%s%s%s" % (autosnapsmf.SNAPLABELPREFIX, + self._labelSeparator, + schedule[0]) + if snapshot.snaplabel.find(label) == 0: + tempSchedule = schedule + break + if tempSchedule == None: + # Backup doesn't belong to a temporary schedule so + # nothing left to do + return True + + keep = tempSchedule[3] # [schedule,interval,period,keep] + schedBackups = [d for d in os.listdir(targetDir) if + d.find(label) == 0] + # The minimum that can be kept around is one: + # keeping zero is stupid since it might trigger + # a total replication rather than an incremental + # rsync replication. + if len(schedBackups) <= 1: + return True + if len(schedBackups) <= keep: + return True + + sortedBackupList = [] + for backup in schedBackups: + stInfo = os.stat(backup) + # List is sorted by mtime, oldest first + insort(sortedBackupList, [stInfo.st_mtime, backup]) + purgeList = sortedBackupList[0:-keep] + + trash = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCTRASHSUFFIX) + if not os.path.exists(trash): + os.makedirs(trash, 0755) + for mtime,dirName in purgeList: + trashDir = os.path.join(trash, + dirName) + # Perform a final sanity check to make sure a backup + # directory and not a system directory is being purged. + # If it doesn't contain the RSYNCDIRSUFFIX string a + # ValueError will be raised. + try: + os.getcwd().index(rsyncsmf.RSYNCDIRSUFFIX) + lockFp = None + lockFile = os.path.join(lockFileDir, + dirName + ".lock") + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't move expired backup %s to trash " \ + "because it is locked by another " \ + "process. Skipping" % (dirName), + self._verbose) + + util.debug("Moving expired rsync backup to trash:" \ + " %s -> %s" % (dirName, trash), + self._verbose) + os.rename(dirName, trashDir) + # Release and delete lock file + lockFp.close() + os.unlink(lockFile) + # Remove its mtime key/value from self._backupTimes + del self._backupTimes[targetDir][dirName] + # Log file needs to be deleted too. + logFile = os.path.join(logDir, + dirName + ".log") + try: + os.stat(logFile) + util.debug("Deleting rsync log file: %s" \ + % (os.path.abspath(logFile)), + self._verbose) + os.unlink(logFile) + except OSError: + util.debug("Expected rsync log file not " \ + "found: %s"\ + % (os.path.abspath(logFile)), + self._verbose) + + except ValueError: + util.log_error(syslog.LOG_ALERT, + "Invalid attempt to delete " \ + "non-backup directory: %s\n" \ + "Placing plugin into " \ + "maintenance state" % (dirName)) + self._smfInst.mark_maintenance() + sys.exit(-1) + return True + +def release_held_snapshots(propName): + """ + Releases dangling user snapshot holds that could + have occured during abnormal termination of a + previous invocation of this command during a + previous rsync transfer. + Returns a list of snapshots that had holds mathcing + propName released. + """ + # First narrow the list down by finding snapshots + # with userref count > 0 + heldList = [] + released = [] + cmd = [zfs.ZFSCMD, "list", "-H", + "-t", "snapshot", + "-o", "userrefs,name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + holdCount,name = line.split() + if int(holdCount) > 0: + heldList.append(name) + # Now check to see if any of those holds + # match 'propName' + for snapName in heldList: + snapshot = zfs.Snapshot(snapName) + holds = snapshot.holds() + try: + holds.index(propName) + snapshot.release(propName) + released.append(snapName) + except ValueError: + pass + return released + + +def backup_name_to_snapshot_name(path): + """Reconstructs the original snapshot based on an + rsync backup's full path. + Returns a zfs snapshot name. + """ + head,snapLabel = os.path.split(path) + sys,nodeName,rel,ver,arch = os.uname() + prefix = os.path.join(rsyncsmf.RSYNCDIRPREFIX, + nodeName + '/') + suffix = rsyncsmf.RSYNCDIRSUFFIX + + rsyncBaseDir,tail = head.split(prefix , 1) + fsName = tail.split(suffix, 1)[0].rstrip('/') + snapshotName = "%s@%s" % (fsName, snapLabel) + return snapshotName + +def list_pending_snapshots(propName): + """ + Lists all snaphots which have 'propName" set locally. + Resulting list is returned sorted in descending order + of creation time (ie.newest first). + Each element in the returned list is tuple of the form: + [creationtime, snapshotname] + """ + results = [] + snaplist = [] + sortsnaplist = [] + # The process for backing up snapshots is: + # Identify all filesystem snapshots that have the (propName) + # property set to "pending" on them. Back them up starting + # with the oldest first. + # + # Unfortunately, there's no single zfs command that can + # output a locally set user property and a creation timestamp + # in one go. So this is done in two passes. The first pass + # identifies snapshots that are tagged as "pending". The + # second pass uses the filtered results from the first pass + # as arguments to zfs(1) to get creation times. + cmd = [zfs.ZFSCMD, "get", "-H", + "-s", "local", + "-o", "name,value", + propName] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + if len(line) > 1: + line = line.split() + results.append(line) + + for name,value in results: + if value != "pending": + # Already backed up. Skip it." + continue + if name.find('@') == -1: + # Not a snapshot, and should not be set on a filesystem/volume + # Ignore it. + util.log_error(syslog.LOG_WARNING, + "Dataset: %s shouldn't have local property: %s" \ + % (name, propName)) + continue + snaplist.append(name) + + # Nothing pending so just return the empty list + if len(snaplist) == 0: + return snaplist + + cmd = [zfs.ZFSCMD, "get", "-p", "-H", + "-o", "value,name", + "creation"] + cmd.extend(snaplist) + + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + ctimeStr,name = line.split() + insort(sortsnaplist, tuple((long(ctimeStr), name))) + sortsnaplist.reverse() + return sortsnaplist + + +def main(argv): + # This command needs to be executed by the super user (root) to + # ensure that rsync has permissions to access all local filesystem + # snapshots and to replicate permissions and ownership on the target + # device + if os.geteuid() != 0: + head,tail = os.path.split(sys.argv[0]) + sys.stderr.write(tail + " can only be executed by root") + sys.exit(-1) + + # This process needs to be run as a system wide single instance + # only at any given time. So create a lockfile in /tmp and try + # to obtain an exclusive lock on it. If we can't then another + # instance is running and already has a lock on it so just exit. + lockFileDir = os.path.normpath(tempfile.gettempdir() + '/' + \ + ".time-slider") + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + lockFile = os.path.join(lockFileDir, 'rsync-backup.lock') + + lockFp = open(lockFile, 'w') + try: + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + sys.exit(2) + + # The SMF fmri of the time-slider plugin instance associated with + # this command needs to be supplied as the argument immeditately + # proceeding the command. ie. argv[1] + try: + pluginFMRI = sys.argv[1] + except IndexError: + # No FMRI provided. Probably a user trying to invoke the command + # from the command line. + sys.stderr.write("No time-slider plugin SMF instance FMRI defined. " \ + "This plugin does not support command line " \ + "execution. Exiting\n") + sys.exit(-1) + + # Open up a syslog session + syslog.openlog(sys.argv[0], 0, syslog.LOG_DAEMON) + + gobject.threads_init() + # Tell dbus to use the gobject mainloop for async ops + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + dbus.mainloop.glib.threads_init() + # Register a bus name with the system dbus daemon + sysbus = dbus.SystemBus() + busName = dbus.service.BusName("org.opensolaris.TimeSlider.plugin.rsync", sysbus) + dbusObj = dbussvc.RsyncBackup(sysbus, \ + "/org/opensolaris/TimeSlider/plugin/rsync") + + mainLoop = gobject.MainLoop() + backupQueue = BackupQueue(pluginFMRI, dbusObj, mainLoop) + gobject.idle_add(backupQueue.backup_snapshot) + mainLoop.run() + sys.exit(0) + diff --git a/usr/share/time-slider/lib/plugin/rsync/backup.py.orig b/usr/share/time-slider/lib/plugin/rsync/backup.py.orig new file mode 100644 index 0000000..540f06a --- /dev/null +++ b/usr/share/time-slider/lib/plugin/rsync/backup.py.orig @@ -0,0 +1,1264 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import os.path +import fcntl +import tempfile +import sys +import subprocess +import statvfs +import time +import threading +import math +import syslog +import gobject +import gio +import dbus +import shutil +import copy +from bisect import insort, bisect_left + +from time_slider import util, zfs, dbussvc, autosnapsmf, timeslidersmf +import rsyncsmf + + +# Set to True if SMF property value of "plugin/command" is "true" +verboseprop = "plugin/verbose" +propbasename = "org.opensolaris:time-slider-plugin" + +class RsyncError(Exception): + """Generic base class for RsyncError + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + self.msg = msg + def __str__(self): + return repr(self.msg) + + +class RsyncTargetDisconnectedError(RsyncError): + """Exception raised when the backup device goes offline during + the rsync transfer. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, source, dest, message): + msg = "Target directory error during rsync backup from " \ + "%s to target \'%s\' Rsync error details:\n%s" \ + % (source, dest, message) + RsyncError.__init__(self, msg) + + +class RsyncTransferInterruptedError(RsyncError): + """Exception raised when the rsync transfer process pid was + interrupted or killed during the rsync transfer. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, source, dest, message): + msg = "Interrputed rsync transfer from %s to %s " \ + "Rsync error details:\n%s" % (source, dest, message) + RsyncError.__init__(self, msg) + + +class RsyncSourceVanishedError(RsyncError): + """Exception raised when rsync could only partially transfer + due to the contents of the source directory being removed. + Possibly due to a snapshot being destroyed during transfer + because of immediate or deferred (holds released) destruction. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, source, dest, message): + msg = "Rsync source directory vanished during transfer of %s to %s" \ + "Rsync error details:\n%s" % (source, dest, message) + RsyncError.__init__(self, msg) + + +class RsyncProcess(threading.Thread): + + + def __init__(self, source, target, latest=None, verbose=False, logfile=None): + + self._sourceDir = source + self._backupDir = target + self._latest = latest + self._verbose = verbose + self._proc = None + self._forkError = None + self._logFile = logfile + # Init done. Now initiaslise threading. + threading.Thread.__init__ (self) + + def run(self): + try: + self._proc = subprocess.Popen(self._cmd, + stderr=subprocess.PIPE, + close_fds=True) + except OSError as e: + # _check_exit_code() will pick up this and raise an + # exception in the original thread. + self._forkError = "%s: %s" % (self._cmd[0], str(e)) + else: + self._stdout,self._stderr = self._proc.communicate() + self._exitValue = self._proc.wait() + + def _check_exit_code(self): + if self._forkError: + # The rsync process failed to execute, probably + # received an OSError exception. Pass it up. + raise RsyncError(self._forkError) + + if self._exitValue == 0: + return + # Non zero return code means rsync encountered an + # error which may be transient or require sys-admin + # intervention to fix. + + # This method basically just maps known rsync exit codes + # to exception classes. + + # Rsync exit value codes (non-zero) + + # 11/12 Indicates backup drive was disconnected during + # transfer. Recoverable once drive reconnected: + # 11 Error in file I/O + # 12 Error in rsync protocol data stream + if self._exitValue == 11 or \ + self._exitValue == 12: + raise RsyncTargetDisconnectedError(self._sourceDir, + self._backupDir, + self._stderr) + # Transfer pid interrupted by SIGUSR1 or SIGINT. Recoverable: + # 20 Received SIGUSR1 or SIGINT + elif self._proc.returncode == 20: + raise RsyncTransferInterruptedError(self._sourceDir, + self._backupDir, + self._stderr) + + # For everything else unknown or unexpected, treat it as + # fatal and provide the rsync stderr output. + else: + raise RsyncError(self._stderr) + + def start_backup(self): + # First, check to see if the rsync destination + # directory is accessible. + try: + os.stat(self._backupDir) + except OSError: + util.debug("Backup directory is not " \ + "currently accessible: %s" \ + % (self._backupDir), + self._verbose) + #FIXME exit/exception needs to be raise here + # or status needs to be set. + return + + try: + os.stat(self._sourceDir) + except OSError: + util.debug("Backup source directory is not " \ + "currently accessible: %s" \ + % (self._sourceDir), + self._verbose) + #FIXME exit/excpetion needs to be raise here + # or status needs to be set. + return + + if self._latest: + self._cmd = ["/usr/bin/rsync", "-a", "--inplace",\ + "%s/." % (self._sourceDir), \ + "--link-dest=%s" % (self._latest), \ + self._backupDir] + else: + self._cmd = ["/usr/bin/rsync", "-a", "--inplace",\ + "%s/." % (self._sourceDir), \ + self._backupDir] + + if self._logFile: + self._cmd.insert(1, "--log-file=%s" % (self._logFile)) + if self._verbose: + self._cmd.insert(1, "-vv") + + self.start() + + +class BackupQueue(): + + def __init__(self, fmri, dbus, mainLoop=None): + self._bus = dbus + self._mainLoop = mainLoop + self._started = False + self._pluginFMRI = fmri + self._smfInst = rsyncsmf.RsyncSMF(self._pluginFMRI) + self._verbose = self._smfInst.get_verbose() + self._rsyncVerbose = self._smfInst.get_rsync_verbose() + self._propName = "%s:%s" % (propbasename, fmri.rsplit(':', 1)[1]) + + # Variables to quickly access time sorted backups and + # to map directory names to mtimes. + self._backupDirs = None + self._backups = None + self._backupTimes = {} + + released = release_held_snapshots(self._propName) + for snapName in released: + util.debug("Released dangling userref on: " + snapName, + self._verbose) + + self._tempSchedules = None + # List of all snapshots currently in our backup queue + self._pendingList = list_pending_snapshots(self._propName) + + # Try to backup in sets of snapshots grouped by a common + # snapshot label. These get taken from the head of the + # pending list. After a working set has been completed, + # pending list gets refreshed and a new working set is + # extracted. + self._currentQueueSet = [] + self._skipList = [] + self._queueLength = 0 + + self._cleanupThreshold = self._smfInst.get_cleanup_threshold() + if self._cleanupThreshold < 1 or \ + self._cleanupThreshold > 99: + # Stupid value. #FIXME Plugin should be checked by SMF start + # method and placed into maintenance if this happens. + # For now, go with default + util.log_error(syslog.LOG_ERR, + "Invalid value for SMF property " \ + ": %d." \ + "Using default value of 95%" \ + % (self._cleanupThreshold)) + + # Base variables for backup device. Will be initialised + # later in _find_backup_device() + self._smfTargetKey = self._smfInst.get_target_key() + sys,self._nodeName,rel,ver,arch = os.uname() + self._rsyncBaseDir = None + self._rsyncDir = None + self._keyFile = None + + tsSMF = timeslidersmf.TimeSliderSMF() + self._labelSeparator = tsSMF.get_separator() + del tsSMF + # Finally go look for the backup device + self._find_backup_device() + + def empty_trash_folders(self): + trashDirs = [] + trashedBackups = [] + os.chdir(self._rsyncDir) + for root, dirs, files in os.walk(self._rsyncDir): + if '.time-slider' in dirs: + dirs.remove ('.time-slider') + trashDir = os.path.join(root, + rsyncsmf.RSYNCTRASHSUFFIX) + if os.path.exists(trashDir): + trashDirs.append(trashDir) + for trashDir in trashDirs: + os.chdir(trashDir) + trashItems = [] + trashItems = [d for d in os.listdir(trashDir) \ + if os.path.isdir(d) and + not os.path.islink(d)] + if len(trashItems) > 0: + util.debug("Deleting trash backups in %s" % (trashDir), + self._verbose) + for trashItem in trashItems: + util.debug("Deleting trash item: %s" % (trashItem), + self._verbose) + # FIXME add some dbus notification here to let the + # applet know what's going on. + shutil.rmtree(trashItem) + + def _get_temp_schedules(self): + # Get retention rule for non archival snapshots as per + # rules defined in: + # svc://system/filesystem/zfs/auto-snapshot: + archived = self._smfInst.get_archived_schedules() + triggers = self._smfInst.get_trigger_list() + defScheds = autosnapsmf.get_default_schedules() + customScheds = autosnapsmf.get_custom_schedules() + try: + triggers.index('all') + # Expand the wildcard value 'all' + triggers = [sched for sched,i,p,k in defScheds] + customTriggers = [sched for sched,i,p,k in customScheds] + triggers.extend(customTriggers) + except ValueError: + pass + + self._tempSchedules = [schedule for schedule in defScheds if \ + schedule[0] not in archived] + self._tempSchedules.extend([schedule for schedule in customScheds if \ + schedule[0] not in archived]) + + def _remove_dead_backups(self): + """ + Identifies and removes partially completed backups whose origin + snapshot is no longer in the pending queue, indicating that the + backup will never get completed, in which case it's just a waste + of space + """ + backupDirs = [] + partialDirs = [] + deadBackups = [] + os.chdir(self._rsyncDir) + for root, dirs, files in os.walk(self._rsyncDir): + if '.time-slider' in dirs: + dirs.remove ('.time-slider') + partialDir = os.path.join(root, + rsyncsmf.RSYNCPARTIALSUFFIX) + partialDirs.append(partialDir) + for dirName in partialDirs: + if not os.path.exists(partialDir): + continue + os.chdir(dirName) + partials = [d for d in os.listdir(dirName) \ + if os.path.isdir(d)] + if len(partials) == 0: + continue + suffix = rsyncsmf.RSYNCPARTIALSUFFIX + prefix = self._rsyncDir + # Reconstruct the origin ZFS filesystem name + baseName = dirName.replace(prefix, '', 1).lstrip('/') + fsName = baseName.replace(suffix, '', 1).rstrip('/') + for snapshotLabel in partials: + pending = False + # Reconstruct the origin snapshot name and see + # if it's still pending rsync backup. If it is + # then leave it alone since it can be used to + # resume a partial backup later. Otherwise it's + # never going to be backed up and needs to be + # manually deleted. + snapshotName = "%s@%s" % (fsName, snapshotLabel) + for ctime,name in self._pendingList: + if name == snapshotName: + pending = True + continue + if pending == False: + util.debug("Deleting zombied partial backup: %s" \ + % (os.path.abspath(snapshotLabel)), + self._verbose) + shutil.rmtree(snapshotLabel) + # Don't forget the log file too. + logFile = os.path.join(os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOGSUFFIX, + snapshotLabel + ".log") + try: + os.stat(logFile) + util.debug("Deleting zombie log file: %s" \ + % (os.path.abspath(logFile)), + self._verbose) + os.unlink(logFile) + except OSError: + util.debug("Expected rsync log file not " \ + "found: %s"\ + % (os.path.abspath(logFile)), + self._verbose) + + def _discover_backups(self): + self._backupDirs = [] + self._backups = [] + self._backupTimes = {} + # backupTimes dictionary is not primarily used + # to store directory listings, but to map + # mtimes to directories for quick lookups. + + os.chdir (self._rsyncDir) + for root, dirs, files in os.walk(self._rsyncDir): + if '.time-slider' in dirs: + dirs.remove('.time-slider') + backupDir = os.path.join(root, rsyncsmf.RSYNCDIRSUFFIX) + if os.path.exists(backupDir): + insort(self._backupDirs, os.path.abspath(backupDir)) + for dirName in self._backupDirs: + self._backupTimes[dirName] = {} + os.chdir(dirName) + dirList = [d for d in os.listdir(dirName) \ + if os.path.isdir(d) and + not os.path.islink(d)] + for d in dirList: + mtime = os.stat(d).st_mtime + insort(self._backups, [long(mtime), os.path.abspath(d)]) + self._backupTimes[dirName][d] = mtime + + def _find_backup_device(self): + # Determine the rsync backup dir. This is the target dir + # defined by the SMF instance plus the "TIMESLIDER/" + # suffix. Try finding it at the preconfigured path first, + # then failing that, scan removable media mounts, in case it + # got remounted under a different path than at setup time. + self._rsyncBaseDir = None + path = self._smfInst.get_target_dir() + if self._validate_rsync_target(path) == True: + self._rsyncBaseDir = path + util.debug("Backup target device online: %s" % (path), + self._verbose) + else: + util.debug("Backup target device not mounted at: %s" \ + "Scanning removable devices.." \ + % (path), + self._verbose) + volMonitor = gio.volume_monitor_get() + mounts = volMonitor.get_mounts() + for mount in mounts: + root = mount.get_root() + path = root.get_path() + if self._validate_rsync_target(path) == True: + util.debug("Located backup target device at: %s" \ + % (path), + self._verbose) + self._rsyncBaseDir = path + + if self._rsyncBaseDir != None: + self._rsyncDir = os.path.join(self._rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + self._keyFile = os.path.join(self._rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + + + def _validate_rsync_target(self, path): + """ + Tests path to see if it is the pre-configured + rsync backup device path. + Returns True on success, otherwise False + """ + # FIXME - this is duplicate in the applet and should + # be moved into a shared module + if not os.path.exists(path): + return False + testDir = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + testKeyFile = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + if os.path.exists(testDir) and \ + os.path.exists(testKeyFile): + testKeyVal = None + f = open(testKeyFile, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetKey = val.strip() + break + f.close() + if targetKey == self._smfTargetKey: + return True + return False + + def _find_deleteable_backups(self, timestamp): + """ + Returns a list of backup directory paths that are older than + timestamp, and can be deleted in order to make room for newer + backups. Certain restrictions apply such as: + - The backup cannot be locked (indicating it's being or about + to be deleted by another process + - The last remaining backup in it's parent directory, which + would mean it is the only backup of a given fileystem on the + device. We always keep at least 1 full backup of a filesystem + on the device if it belongs to a currently selected filesystem. + """ + deleteables = [] + # This should have already occured in + # backup_snapshot() mainloop method + if self._backupDirs == None: + self._discover_backups() + + if len(self._backupDirs) == 0: + # We were not able to find any backups to delete. Try again later + return [] + + # Extract the subset of self._backupDirs that are older than timestamp + idx = bisect_left(self._backups, [timestamp, '']) + subset = self._backups[0:idx] + + # Copy the backupTimes dictionary since so we can + # modify it safely. + copiedTimes = copy.deepcopy(self._backupTimes) + for mtime, dirName in subset: + head,tail = os.path.split(dirName) + if len(copiedTimes[head]) < 2: + # We can only delete this single backup provided + # it's filesystem is no longer tagged for rsync + # replication. Othewise we need to leave at least + # one backup on the device at all times, and also + # to provide an incremental backup point for + # future backups. + snapName = backup_name_to_snapshot_name(dirName) + snapshot = zfs.Snapshot(snapName) + fs = zfs.Filesystem(snapshot.fsname) + if fs.get_user_property(rsyncsmf.RSYNCFSTAG) == "true": + continue + else: + # If we are going to actually remove this final + # remaining backup of this filesystem then we need + # to unqueue any pending snapshots of it, + # otherwise after deleting this one, we'll just + # start backing up the older snapshots of this + # filesystem afterwards, which is a waste of time + # and space. + pending = [name for time,name in \ + list_pending_snapshots(self._propName) if \ + name.find(snapshot.fsname + '@') == 0] + cmd = [zfs.PFCMD, zfs.ZFSCMD, "inherit", self._propName] + util.debug("Unqueuing pending backups of deselected " \ + "filesystem: " + snapshot.fsname + '\n' + \ + str(pending), + self._verbose) + cmd.extend(pending) + util.run_command(cmd) + + lockFileDir = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOCKSUFFIX) + lockFile = os.path.join(lockFileDir, tail) + + if not os.path.exists(lockFile): + # No lock file so we are free to delete it. + deleteables.append([mtime, dirName]) + # Remove it from copiedTimes so it doesn't get + # double counted + del copiedTimes[head][tail] + continue + # Lock file so probably can't delete this, but try it out + # anyway incase it's stale/unlocked + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't delete backup %s because it " \ + "is locked by another process." \ + "Skipping" % (dirName), + self._verbose) + continue + # Ok, we can still delete it, but get rid of the stale lock file + lockFp.close() + os.unlink(lockFile) + deleteabled.append([mtime, dirName]) + # Remove it from copiedTimes so it doesn't get + # double counted + del copiedTimes[head][tail] + return deleteables + + def _recover_space(self, deleteables): + + # Important: + # Don't actually loop throught this list fully. Break out + # as soon as pool capacity is beneath the threshhold level + # again. + remainder = deleteables[:] + for mtime,dirName in deleteables: + if util.get_filesystem_capacity(self._rsyncDir) < \ + self._cleanupThreshold: + # No need to delete anything further + return remainder + lockFile = None + lockFp = None + head,tail = os.path.split(dirName) + + # Check if it has a lock file and try to grab it. + # If that fails we need to move onto the next backup in + # the list. + lockFileDir = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOCKSUFFIX) + lockFile = os.path.join(lockFileDir, tail) + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't delete backup %s because it " \ + "is locked by another process." \ + "Skipping" % (dirName), + self._verbose) + # Remove if from the remainder list + idx = bisect_left(remainder, [mtime, dirName]) + del remainder[idx] + continue + + trash = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCTRASHSUFFIX) + trashDir = os.path.join(trash, tail) + + if not os.path.exists(trash): + os.makedirs(trash, 0755) + + util.debug("Deleting rsync backup to recover space: %s"\ + % (dirName), self._verbose) + os.rename(dirName, trashDir) + lockFp.close() + os.unlink(lockFile) + shutil.rmtree(trashDir) + # Remove the log file if it exists + logFile = os.path.join(head, + os.path.pardir, + os.path.pardir, + rsyncsmf.RSYNCLOGSUFFIX, + tail + ".log") + if os.path.exists(logFile): + os.unlink(logFile) + else: + util.debug("Expected to find log file %s when deleting %s " \ + "during space recovery" % (logFile, dirName), + self._verbose) + # Remove dirName from the backup list + idx = bisect_left(self._backups, [mtime, dirName]) + del self._backups[idx] + # Remove if from the remainder list too + idx = bisect_left(remainder, [mtime, dirName]) + del remainder[idx] + return remainder + + def backup_snapshot(self): + # First, check to see if the rsync destination + # directory is accessible. + + if self._rsyncBaseDir == None: + util.debug("Backup target device is not " \ + "accessible right now: %s" \ + % (self._smfInst.get_target_dir()), + self._verbose) + self._bus.rsync_unsynced(len(self._pendingList)) + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + + # Extra paranoia + if self._validate_rsync_target(self._rsyncBaseDir) == False: + util.debug("Backup target directory does not " \ + "have a matching configuration key. " \ + "Possibly old or wrong device: %s" \ + % (self._keyFile), + self._verbose) + self._bus.rsync_unsynced(len(self._pendingList)) + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + + # Before getting started. Identify what needs to be cleaned up on + # the backup target. + if self._tempSchedules == None: + self._get_temp_schedules() + + # Remove incompleteable partial backups, then find out what + # complete backups we already have on the target device + if self._started == False: + self._remove_dead_backups() + self._discover_backups() + + if len(self._currentQueueSet) == 0: + # Means we are just getting started or have just completed + # backup of one full set of snapshots. Clear out anything + # we may have moved to the trash during the previous + # backup set iterations. + self.empty_trash_folders() + # Refresh the pending list and build a new working set queue + self._pendingList = list_pending_snapshots(self._propName) + # Remove skipped items to avoid infinite looping. + for item in self._skipList: + try: + self._pendingList.remove(item) + except ValueError: + pass + if len(self._pendingList) == 0: + # If something was actually backed up, signal + # that it is now completed. + if self._started == True: + self._bus.rsync_complete(self._rsyncBaseDir) + self._bus.rsync_synced() + # Nothing to do exit + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + else: + # Construct a new working set queue + # Identify the newest snapshot and then all + # snapshots with a matching snapshot label + self._queueLength = len(self._pendingList) + ctime,headSnapName = self._pendingList[0] + label = headSnapName.rsplit("@", 1)[1] + self._currentQueueSet = \ + [(ctime,snapName) for \ + ctime,snapName in self._pendingList if \ + snapName.rsplit("@", 1)[1] == label] + + + if len(self._backups) > 0: + oldestBackupTime, oldestBackup = self._backups[0] + qTime, qItem = self._currentQueueSet[0] + + # If the backup device is nearly full, don't + # bother trying to backup anything unless it's + # newer than the oldest backup set already on + # the device. + # This avoids potential situations where if the + # backup device has severely limited capacity and + # older backups sets were in the queue, newer backups + # might get deleted in order to make room for older + # ones, creating a downward spiral. + capacity = util.get_filesystem_capacity(self._rsyncDir) + if capacity > self._cleanupThreshold: + # Find backups older than qTime that could in theory + # be deleted in order to make room for the curtent + # pending item. + deleteables = self._find_deleteable_backups(qTime) + + if len(deleteables) == 0 and \ + qTime < oldestBackupTime: + util.debug("%s has exceeded %d%% of its capacity. " \ + "Skipping pending backups prior to: " \ + "%s" % (self._rsyncDir, + self._cleanupThreshold, + time.ctime(oldestBackupTime)), + self._verbose) + if self._started == True: + self._bus.rsync_complete(self._rsyncBaseDir) + self._bus.rsync_synced() + # Nothing to do exit + if self._mainLoop: + self._mainLoop.quit() + sys.exit(0) + + if self._started == False: + self._started = True + self._bus.rsync_started(self._rsyncBaseDir) + + ctime,snapName = self._currentQueueSet[0] + snapshot = zfs.Snapshot(snapName, long(ctime)) + # Make sure the snapshot didn't get destroyed since we last + # checked it. + remainingList = self._currentQueueSet[1:] + if snapshot.exists() == False: + util.debug("Snapshot: %s no longer exists. Skipping" \ + % (snapName), self._verbose) + self._currentQueueSet = remainingList + return True + + # Place a hold on the snapshot so it doesn't go anywhere + # while rsync is trying to back it up. + snapshot.hold(self._propName) + self._queueLength -= 1 + fs = zfs.Filesystem(snapshot.fsname) + sourceDir = None + if fs.is_mounted() == True: + # Get the mountpoint + mountPoint = fs.get_mountpoint() + sourceDir = "%s/.zfs/snapshot/%s" \ + % (mountPoint, snapshot.snaplabel) + else: + # If the filesystem is not mounted just skip it. If it's + # not mounted then nothing is being written to it. And + # we can just catch up with it again later if it doesn't + # get expired by time-sliderd + util.debug("%s is not mounted. Skipping." \ + % (snapshot.fsname), self._verbose) + snapshot.release(self._propName) + self._skipList.append(ctime, snapName) + self._currentQueueSet = remainingList + return True + + # targetDir is the parent folder of all backups + # for a given filesystem + targetDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCDIRSUFFIX) + # partialDir is a separate directory in which + # snapshots are initially backed up to. Upon successful + # completion they are moved to the backupDir. + partialDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCPARTIALSUFFIX, + snapshot.snaplabel) + lockFileDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCLOCKSUFFIX) + logDir = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCLOGSUFFIX) + logFile = os.path.join(logDir, + snapshot.snaplabel + ".log") + + + # backupDir is the full directory path where the new + # backup will be located ie / + backupDir = os.path.join(targetDir, snapshot.snaplabel) + + # Figure out the closest previous backup. Since we + # backup newest first instead of oldest first it's + # determined as follows: + # If queued backup item is newer than the most recent + # backup on the backup target, use the most recent + # backup as the incremental source. + # Othewise identify the backup on the device that is + # nearest to but newer than the queued backup. + nearestOlder = None + nearestNewer = None + dirList = [] + + if not os.path.exists(partialDir): + os.makedirs(partialDir, 0755) + if not os.path.exists(logDir): + os.makedirs(logDir, 0755) + + if not os.path.exists(targetDir): + os.makedirs(targetDir, 0755) + # Add the new directory to our internal + # mtime dictionary and sorted list. + self._backupTimes[targetDir] = {} + insort(self._backupDirs, targetDir) + else: + for name,value in self._backupTimes[targetDir].items(): + if ctime > value: + if nearestOlder == None or \ + value > nearestOlder[1]: + nearestOlder = [name, value] + else: + if nearestNewer == None or \ + value < nearestNewer[1]: + nearestNewer = [name, value] + + os.chdir(targetDir) + link = None + linkDest = None + lockFile = None + lockFp = None + if nearestNewer: + link = nearestNewer[0] + elif nearestOlder: + link = nearestOlder[0] + if link: + linkDest = os.path.realpath(link) + # Create a lock for linkDest. We need to ensure that + # nautilus' restore view or the time-slider-delete + # GUI doesn't attempt to delete it or move it to the + # trash while it is being used by rsync for incremental + # backup. + lockFile = os.path.join(lockFileDir, + link + ".lock") + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't perform incremental rsync of %s because " \ + "unable to obtain exclusive lock on incremental " \ + "backup reference point: %s. Exiting" \ + % (lockFile), self._verbose) + os.chdir("/") + snapshot.release(self._propName) + sys.exit(1) + + self._rsyncProc = RsyncProcess(sourceDir, + partialDir, + linkDest, + self._rsyncVerbose, + logFile) + + # Notify the applet of current status via dbus + self._bus.rsync_current(snapshot.name, self._queueLength) + + # Set umask temporarily so that rsync backups are read-only to + # the owner by default. Rync will override this to match the + # permissions of each snapshot as appropriate. + origmask = os.umask(0222) + util.debug("Starting rsync backup of '%s' to: %s" \ + % (sourceDir, partialDir), + self._verbose) + self._rsyncProc.start_backup() + + warningDone = False + while self._rsyncProc.is_alive(): + if len(self._backups) > 0: + # Monitor backup target capacity while we wait for rsync. + capacity = util.get_filesystem_capacity(self._rsyncDir) + if capacity > self._cleanupThreshold: + # Find backups older than qTime that could in theory + # be deleted in order to make room for the curtent + # pending item. + deleteables = self._find_deleteable_backups(qTime) + # Only generate annoying debug message once instead of + # every 5 seconds. + if warningDone == False: + util.debug("Backup device capacity exceeds %d%%. " \ + "Found %d deleteable backups for space " \ + "recovery." \ + % (capacity, len(deleteables)), + self._verbose) + warningDone = True + if len(deleteables) > 0: + deleteables = self._recover_space(deleteables) + time.sleep(5) + + try: + self._rsyncProc._check_exit_code() + except (RsyncTransferInterruptedError, + RsyncTargetDisconnectedError, + RsyncSourceVanishedError) as e: + os.chdir("/") + snapshot.release(self._propName) + util.log_error(syslog.LOG_ERR, str(e)) + # These are recoverable, so exit for now and try again + # later + sys.exit(-1) + + except RsyncError as e: + # If the backup device went offline we need to chdir + # out of it or running further commands might fail. + os.chdir("/") + util.log_error(syslog.LOG_ERR, + "Unexpected rsync error encountered: \n" + \ + str(e)) + util.log_error(syslog.LOG_ERR, + "Rsync log file location: %s" \ + % (os.path.abspath(logFile))) + util.log_error(syslog.LOG_ERR, + "Placing plugin into maintenance mode") + self._smfInst.mark_maintenance() + snapshot.release(self._propName) + sys.exit(-1) + + finally: + if lockFp: + lockFp.close() + os.unlink(lockFile) + + util.debug("Rsync process exited", self._verbose) + os.umask(origmask) + + # Move the completed backup from the partial dir to the + # the propert backup directory + util.debug("Renaming completed backup from %s to %s" \ + % (partialDir, backupDir), self._verbose) + os.rename(partialDir, backupDir) + + # Reset the mtime and atime properties of the backup directory so that + # they match the snapshot creation time. This is extremely important + # because the backup mechanism relies on it to determine backup times + # and nearest matches for incremental rsync (linkDest) + os.utime(backupDir, (long(ctime), long(ctime))) + # Update the dictionary and time sorted list with ctime also + self._backupTimes[targetDir][snapshot.snaplabel] = long(ctime) + insort(self._backups, [long(ctime), os.path.abspath(backupDir)]) + snapshot.set_user_property(self._propName, "completed") + snapshot.release(self._propName) + self._currentQueueSet = remainingList + + # Now is a good time to clean out the directory: + # Check to see if the backup just completed belonged to an + # auto-snapshot schedule and whether older backups should get + # deleted. + if snapshot.snaplabel.find(autosnapsmf.SNAPLABELPREFIX) == 0: + tempSchedule = None + label = None + for schedule in self._tempSchedules: + label = "%s%s%s" % (autosnapsmf.SNAPLABELPREFIX, + self._labelSeparator, + schedule[0]) + if snapshot.snaplabel.find(label) == 0: + tempSchedule = schedule + break + if tempSchedule == None: + # Backup doesn't belong to a temporary schedule so + # nothing left to do + return True + + keep = tempSchedule[3] # [schedule,interval,period,keep] + schedBackups = [d for d in os.listdir(targetDir) if + d.find(label) == 0] + + + schedBackups = [d for d in dirList if + d.find(label) == 0] + # The minimum that can be kept around is one: + # keeping zero is stupid since it might trigger + # a total replication rather than an incremental + # rsync replication. + if len(schedBackups) <= 1: + return True + if len(schedBackups) <= keep: + return True + + sortedBackupList = [] + for backup in schedBackups: + stInfo = os.stat(backup) + # List is sorted by mtime, oldest first + insort(sortedBackupList, [stInfo.st_mtime, backup]) + purgeList = sortedBackupList[0:-keep] + + trash = os.path.join(self._rsyncDir, + snapshot.fsname, + rsyncsmf.RSYNCTRASHSUFFIX) + if not os.path.exists(trash): + os.makedirs(trash, 0755) + for mtime,dirName in purgeList: + trashDir = os.path.join(trash, + dirName) + # Perform a final sanity check to make sure a backup + # directory and not a system directory is being purged. + # If it doesn't contain the RSYNCDIRSUFFIX string a + # ValueError will be raised. + try: + os.getcwd().index(rsyncsmf.RSYNCDIRSUFFIX) + lockFp = None + lockFile = os.path.join(lockFileDir, + dirName + ".lock") + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + util.debug("Can't move expired backup %s to trash " \ + "because it is locked by another " \ + "process. Skipping" % (dirName), + self._verbose) + + util.debug("Moving expired rsync backup to trash:" \ + " %s -> %s" % (dirName, trash), + self._verbose) + os.rename(dirName, trashDir) + # Release and delete lock file + lockFp.close() + os.unlink(lockFile) + # Remove its mtime key/value from self._backupTimes + del self._backupTimes[targetDir][dirName] + # Log file needs to be deleted too. + logFile = os.path.join(logDir, + dirName + ".log") + try: + os.stat(logFile) + util.debug("Deleting rsync log file: %s" \ + % (os.path.abspath(logFile)), + self._verbose) + os.unlink(logFile) + except OSError: + util.debug("Expected rsync log file not " \ + "found: %s"\ + % (os.path.abspath(logFile)), + self._verbose) + + except ValueError: + util.log_error(syslog.LOG_ALERT, + "Invalid attempt to delete " \ + "non-backup directory: %s\n" \ + "Placing plugin into " \ + "maintenance state" % (dirName)) + self._smfInst.mark_maintenance() + sys.exit(-1) + return True + +def release_held_snapshots(propName): + """ + Releases dangling user snapshot holds that could + have occured during abnormal termination of a + previous invocation of this command during a + previous rsync transfer. + Returns a list of snapshots that had holds mathcing + propName released. + """ + # First narrow the list down by finding snapshots + # with userref count > 0 + heldList = [] + released = [] + cmd = [zfs.ZFSCMD, "list", "-H", + "-t", "snapshot", + "-o", "userrefs,name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + holdCount,name = line.split() + if int(holdCount) > 0: + heldList.append(name) + # Now check to see if any of those holds + # match 'propName' + for snapName in heldList: + snapshot = zfs.Snapshot(snapName) + holds = snapshot.holds() + try: + holds.index(propName) + snapshot.release(propName) + released.append(snapName) + except ValueError: + pass + return released + + +def backup_name_to_snapshot_name(path): + """Reconstructs the original snapshot based on an + rsync backup's full path. + Returns a zfs snapshot name. + """ + head,snapLabel = os.path.split(path) + sys,nodeName,rel,ver,arch = os.uname() + prefix = os.path.join(rsyncsmf.RSYNCDIRPREFIX, + nodeName + '/') + suffix = rsyncsmf.RSYNCDIRSUFFIX + + rsyncBaseDir,tail = head.split(prefix , 1) + fsName = tail.split(suffix, 1)[0].rstrip('/') + snapshotName = "%s@%s" % (fsName, snapLabel) + return snapshotName + +def list_pending_snapshots(propName): + """ + Lists all snaphots which have 'propName" set locally. + Resulting list is returned sorted in descending order + of creation time (ie.newest first). + Each element in the returned list is tuple of the form: + [creationtime, snapshotname] + """ + results = [] + snaplist = [] + sortsnaplist = [] + # The process for backing up snapshots is: + # Identify all filesystem snapshots that have the (propName) + # property set to "pending" on them. Back them up starting + # with the oldest first. + # + # Unfortunately, there's no single zfs command that can + # output a locally set user property and a creation timestamp + # in one go. So this is done in two passes. The first pass + # identifies snapshots that are tagged as "pending". The + # second pass uses the filtered results from the first pass + # as arguments to zfs(1) to get creation times. + cmd = [zfs.ZFSCMD, "get", "-H", + "-s", "local", + "-o", "name,value", + propName] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + if len(line) > 1: + line = line.split() + results.append(line) + + for name,value in results: + if value != "pending": + # Already backed up. Skip it." + continue + if name.find('@') == -1: + # Not a snapshot, and should not be set on a filesystem/volume + # Ignore it. + util.log_error(syslog.LOG_WARNING, + "Dataset: %s shouldn't have local property: %s" \ + % (name, propName)) + continue + snaplist.append(name) + + # Nothing pending so just return the empty list + if len(snaplist) == 0: + return snaplist + + cmd = [zfs.ZFSCMD, "get", "-p", "-H", + "-o", "value,name", + "creation"] + cmd.extend(snaplist) + + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + ctimeStr,name = line.split() + insort(sortsnaplist, tuple((long(ctimeStr), name))) + sortsnaplist.reverse() + return sortsnaplist + + +def main(argv): + # This command needs to be executed by the super user (root) to + # ensure that rsync has permissions to access all local filesystem + # snapshots and to replicate permissions and ownership on the target + # device + if os.geteuid() != 0: + head,tail = os.path.split(sys.argv[0]) + sys.stderr.write(tail + " can only be executed by root") + sys.exit(-1) + + # This process needs to be run as a system wide single instance + # only at any given time. So create a lockfile in /tmp and try + # to obtain an exclusive lock on it. If we can't then another + # instance is running and already has a lock on it so just exit. + lockFileDir = os.path.normpath(tempfile.gettempdir() + '/' + \ + ".time-slider") + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + lockFile = os.path.join(lockFileDir, 'rsync-backup.lock') + + lockFp = open(lockFile, 'w') + try: + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + sys.exit(2) + + # The SMF fmri of the time-slider plugin instance associated with + # this command needs to be supplied as the argument immeditately + # proceeding the command. ie. argv[1] + try: + pluginFMRI = sys.argv[1] + except IndexError: + # No FMRI provided. Probably a user trying to invoke the command + # from the command line. + sys.stderr.write("No time-slider plugin SMF instance FMRI defined. " \ + "This plugin does not support command line " \ + "execution. Exiting\n") + sys.exit(-1) + + # Open up a syslog session + syslog.openlog(sys.argv[0], 0, syslog.LOG_DAEMON) + + gobject.threads_init() + # Tell dbus to use the gobject mainloop for async ops + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + dbus.mainloop.glib.threads_init() + # Register a bus name with the system dbus daemon + sysbus = dbus.SystemBus() + busName = dbus.service.BusName("org.opensolaris.TimeSlider.plugin.rsync", sysbus) + dbusObj = dbussvc.RsyncBackup(sysbus, \ + "/org/opensolaris/TimeSlider/plugin/rsync") + + mainLoop = gobject.MainLoop() + backupQueue = BackupQueue(pluginFMRI, dbusObj, mainLoop) + gobject.idle_add(backupQueue.backup_snapshot) + mainLoop.run() + sys.exit(0) + diff --git a/usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py b/usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py new file mode 100755 index 0000000..e29ee8e --- /dev/null +++ b/usr/share/time-slider/lib/plugin/rsync/rsyncsmf.py @@ -0,0 +1,89 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import subprocess +import threading +#from string import letters, digits +from plugin import pluginsmf + +RSYNCPROPGROUP = "rsync" +RSYNCDIRPREFIX = "TIMESLIDER" +RSYNCDIRSUFFIX = ".time-slider/rsync" +RSYNCPARTIALSUFFIX = ".time-slider/.rsync-partial" +RSYNCTRASHSUFFIX = ".time-slider/.trash" +RSYNCLOCKSUFFIX = ".time-slider/.rsync-lock" +RSYNCLOGSUFFIX = ".time-slider/.rsync-log" +RSYNCCONFIGFILE = ".rsync-config" +RSYNCFSTAG = "org.opensolaris:time-slider-rsync" + +class RsyncSMF(pluginsmf.PluginSMF): + + def __init__(self, instanceName): + pluginsmf.PluginSMF.__init__(self, instanceName) + self._archivedSchedules = None + + def get_cleanup_threshold(self): + result = self.get_prop(RSYNCPROPGROUP, "cleanup_threshold").strip() + return int(result) + + def get_target_dir(self): + result = self.get_prop(RSYNCPROPGROUP, "target_dir").strip() + # Strip out '\' characters inserted by svcprop + return result.strip().replace('\\', '') + + def get_target_key(self): + return self.get_prop(RSYNCPROPGROUP, "target_key").strip() + + def set_target_dir(self, path): + self.set_string_prop(RSYNCPROPGROUP, "target_dir", path) + + def set_target_key(self, key): + self.set_string_prop(RSYNCPROPGROUP, "target_key", key) + + def get_archived_schedules(self): + #FIXME Use mutex locking to make MT-safe + if self._archivedSchedules == None: + self._archivedSchedules = [] + value = self.get_prop(RSYNCPROPGROUP, "archived_schedules") + + # Strip out '\' characters inserted by svcprop + archiveList = value.strip().replace('\\', '').split(',') + for schedule in archiveList: + self._archivedSchedules.append(schedule.strip()) + return self._archivedSchedules + + def get_rsync_verbose(self): + value = self.get_prop(RSYNCPROPGROUP, "verbose") + if value == "true": + return True + else: + return False + + def __str__(self): + ret = "SMF Instance:\n" +\ + "\tName:\t\t\t%s\n" % (self.instance_name) +\ + "\tState:\t\t\t%s\n" % (self.svcstate) + \ + "\tTriggers:\t\t%s\n" % str(self.get_triggers()) + \ + "\tTarget Dir:\t%s\n" % self.get_target_dir() + \ + "\tVerbose:\t\t\'%s\'" % str((self.get_verbose())) + return ret + diff --git a/usr/share/time-slider/lib/plugin/rsync/trigger.py b/usr/share/time-slider/lib/plugin/rsync/trigger.py new file mode 100644 index 0000000..0616cb8 --- /dev/null +++ b/usr/share/time-slider/lib/plugin/rsync/trigger.py @@ -0,0 +1,138 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import sys +import subprocess +import syslog + +import rsyncsmf +from time_slider import util, smf, zfs + +# Set to True if SMF property value of "plugin/command" is "true" +verboseprop = "plugin/verbose" +propbasename = "org.opensolaris:time-slider-plugin" + + +def main(argv): + # Check that appropriate environment variables have been + # provided by time-sliderd + # + # The label used for the snapshot set just taken, ie. the + # component proceeding the "@" in the snapshot name + snaplabel = os.getenv("AUTOSNAP_LABEL") + # The SMF fmri of the auto-snapshot instance corresponding to + # the snapshot set just taken. + snapfmri = os.getenv("AUTOSNAP_FMRI") + # The SMF fmri of the time-slider plugin instance associated with + # this command. + pluginfmri = os.getenv("PLUGIN_FMRI") + + if pluginfmri == None: + sys.stderr.write("No time-slider plugin SMF instance FMRI defined. " \ + "This plugin does not support command line " + "execution. Exiting\n") + sys.exit(-1) + syslog.openlog(pluginfmri, 0, syslog.LOG_DAEMON) + + cmd = [smf.SVCPROPCMD, "-p", verboseprop, pluginfmri] + outdata,errdata = util.run_command(cmd) + if outdata.rstrip() == "true": + verbose = True + else: + verbose = False + + if snaplabel == None: + log_error(syslog.LOG_ERR, + "No snapshot label provided. Exiting") + sys.exit(-1) + if snapfmri == None: + log_error(syslog.LOG_ERR, + "No auto-snapshot SMF instance FMRI provided. Exiting") + sys.exit(-1) + + schedule = snapfmri.rsplit(':', 1)[1] + plugininstance = pluginfmri.rsplit(':', 1)[1] + + # The user property/tag used when tagging and holding zfs datasets + propname = "%s:%s" % (propbasename, plugininstance) + + # Identifying snapshots is a 3 stage process. + # + # First: identify all snapshots matching the AUTOSNAP_LABEL + # value passed in by the time-slider daemon. + # + # Second: Filter out snapshots of volumes, since rsync can only + # back up filesystems. + # + # Third: we need to filter the results and ensure that the + # filesystem corresponding to each snapshot is actually + # tagged with the property (com.sun:auto-snapshot<:schedule>) + # + # This is necessary to avoid confusion whereby a snapshot might + # have been sent|received from one zpool to another on the same + # system. The received snapshot will show up in the first pass + # results but is not actually part of the auto-snapshot set + # created by time-slider. It also avoids incorrectly placing + # zfs holds on the imported snapshots. + + + datasets = zfs.Datasets() + candidates = datasets.list_snapshots(snaplabel) + autosnapsets = datasets.list_auto_snapshot_sets(schedule) + autosnapfs = [name for [name,mount] in datasets.list_filesystems() \ + if name in autosnapsets] + snappeddatasets = [] + snapnames = [name for [name,ctime] in candidates \ + if name.split('@',1)[0] in autosnapfs] + + # Mark the snapshots with a user property. Doing this instead of + # placing a physical hold on the snapshot allows time-slider to + # expire the snapshots naturally or destroy them if a zpool fills + # up and triggers a remedial cleanup. + # It also prevents the possiblity of leaving snapshots lying around + # indefinitely on the system if the plugin SMF instance becomes + # disabled or having to release a pile of held snapshots. + # We set org.opensolaris:time-slider-plugin: to "pending", + # indicate + snapshots = [] + for snap in snapnames: + snapshot = zfs.Snapshot(snap) + fs = zfs.Filesystem(snapshot.fsname) + if fs.get_user_property(rsyncsmf.RSYNCFSTAG) == "true": + if fs.is_mounted() == True: + snapshot.set_user_property(propname, "pending") + util.debug("Marking %s as pending rsync" % (snap), verbose) + else: + util.debug("Ignoring snapshot of unmounted fileystem: %s" \ + % (snap), verbose) + +def maintenance(svcfmri): + log_error(syslog.LOG_ERR, + "Placing plugin into maintenance state") + cmd = [smf.SVCADMCMD, "mark", "maintenance", svcfmri] + subprocess.Popen(cmd, close_fds=True) + +def log_error(loglevel, message): + syslog.syslog(loglevel, message + '\n') + sys.stderr.write(message + '\n') + diff --git a/usr/share/time-slider/lib/plugin/zfssend/__init__.py b/usr/share/time-slider/lib/plugin/zfssend/__init__.py new file mode 100644 index 0000000..a2667b2 --- /dev/null +++ b/usr/share/time-slider/lib/plugin/zfssend/__init__.py @@ -0,0 +1,25 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, pardir)) + diff --git a/usr/share/time-slider/lib/plugin/zfssend/zfssend.py b/usr/share/time-slider/lib/plugin/zfssend/zfssend.py new file mode 100644 index 0000000..42f3975 --- /dev/null +++ b/usr/share/time-slider/lib/plugin/zfssend/zfssend.py @@ -0,0 +1,241 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import sys +import subprocess +import syslog +from bisect import insort + +import time_slider.util +import time_slider.smf +import time_slider.zfs + +# Set to True if SMF property value of "plugin/command" is "true" +verboseprop = "plugin/verbose" +propbasename = "org.opensolaris:time-slider-plugin" +print _("Do I work?") + +def main(argv): + + # Check appropriate environment variables habe been supplied + # by time-slider + # + # The label used for the snapshot set just taken, ie. the + # component proceeding the "@" in the snapshot name + snaplabel = os.getenv("AUTOSNAP_LABEL") + # The SMF fmri of the auto-snapshot instance corresponding to + # the snapshot set just taken. + snapfmri = os.getenv("AUTOSNAP_FMRI") + # The SMF fmri of the time-slider plugin instance associated with + # this command. + pluginfmri = os.getenv("PLUGIN_FMRI") + + if pluginfmri == None: + sys.stderr.write("No time-slider plugin SMF instance FMRI defined. " \ + "This plugin does not support command line " + "execution. Exiting\n") + sys.exit(-1) + syslog.openlog(pluginfmri, 0, syslog.LOG_DAEMON) + + cmd = [smf.SVCPROPCMD, "-p", verboseprop, pluginfmri] + outdata,errdata = util.run_command(cmd) + if outdata.rstrip() == "true": + verbose = True + else: + verbose = False + + if snaplabel == None: + log_error(syslog.LOG_ERR, + "No snapshot label defined. Exiting") + sys.exit(-1) + if snapfmri == None: + log_error(syslog.LOG_ERR, + "No auto-snapshot SMF instance FMRI defined. Exiting") + sys.exit(-1) + + schedule = snapfmri.rsplit(':', 1)[1] + plugininstance = pluginfmri.rsplit(':', 1)[1] + + # The user property/tag used when tagging and holding zfs datasets + propname = "%s:%s" % (propbasename, plugininstance) + + # Identifying snapshots is a two stage process. + # + # First: identify all snapshots matching the AUTOSNAP_LABEL + # value passed in by the time-slider daemon. + # + # Second: we need to filter the results and ensure that the + # filesystem/voluem corresponding to each snapshot is actually + # tagged with the property (com.sun:auto-snapshot<:schedule>) + # + # This is necessary to avoid confusion whereby a snapshot might + # have been sent|received from one zpool to another on the same + # system. The received snapshot will show up in the first pass + # results but is not actually part of the auto-snapshot set + # created by time-slider. It also avoids incorrectly placing + # zfs holds on the imported snapshots. + + datasets = zfs.Datasets() + candidates = datasets.list_snapshots(snaplabel) + originsets = datasets.list_auto_snapshot_sets(schedule) + snappeddatasets = [] + snapnames = [name for [name,ctime] in candidates \ + if name.split('@',1)[0] in originsets] + + + # Place a hold on the the newly created snapshots so + # they can be backed up without fear of being destroyed + # before the backup gets a chance to complete. + for snap in snapnames: + snapshot = zfs.Snapshot(snap) + holds = snapshot.holds() + try: + holds.index(propname) + except ValueError: + util.debug("Placing hold on %s" % (snap), verbose) + snapshot.hold(propname) + datasetname = snapshot.fsname + # Insert datasetnames in alphabetically sorted order because + # zfs receive falls over if it receives a child before the + # parent if the "-F" option is not used. + insort(snappeddatasets, datasetname) + + # Find out the receive command property value + cmd = [smf.SVCPROPCMD, "-c", "-p", "receive/command", pluginfmri] + outdata,errdata = util.run_command(cmd) + # Strip out '\' characters inserted by svcprop + recvcmd = outdata.strip().replace('\\', '').split() + + # Check to see if the receive command is accessible and executable + try: + statinfo = os.stat(recvcmd[0]) + other_x = (statinfo.st_mode & 01) + if other_x == 0: + log_error(syslog.LOG_ERR, + "Plugin: %s: Configured receive/command is not " \ + "executable: %s" \ + % (pluginfmri, outdata)) + maintenance(pluginfmri) + sys.exit(-1) + except OSError: + log_error(syslog.LOG_ERR, + "Plugin: %s: Can not access the configured " \ + "receive/command: %s" \ + % (pluginfmri, outdata)) + maintenance(pluginfmri) + sys.exit(-1) + + for dataset in snappeddatasets: + sendcmd = None + prevsnapname = None + ds = zfs.ReadableDataset(dataset) + prevlabel = ds.get_user_property(propname) + + snapname = "%s@%s" % (ds.name, snaplabel) + if (prevlabel == None or len(prevlabel) == 0): + # No previous backup - send a full replication stream + sendcmd = [zfs.ZFSCMD, "send", snapname] + util.debug("No previous backup registered for %s" % ds.name, verbose) + else: + # A record of a previous backup exists. + # Check that it exists to enable send of an incremental stream. + prevsnapname = "%s@%s" % (ds.name, prevlabel) + util.debug("Previously sent snapshot: %s" % prevsnapname, verbose) + prevsnap = zfs.Snapshot(prevsnapname) + if prevsnap.exists(): + sendcmd = [zfs.ZFSCMD, "send", "-i", prevsnapname, snapname] + else: + # This should not happen under normal operation since we + # place a hold on the snapshot until it gets sent. So + # getting here suggests that something else released the + # hold on the snapshot, allowing it to get destroyed + # prematurely. + log_error(syslog.LOG_ERR, + "Previously sent snapshot no longer exists: %s" \ + % prevsnapname) + maintenance(pluginfmri) + sys.exit(-1) + + + # Invoke the send and receive commands via pfexec(1) since + # we are not using the role's shell to take care of that + # for us. + sendcmd.insert(0, smf.PFCMD) + recvcmd.insert(0, smf.PFCMD) + + try: + sendP = subprocess.Popen(sendcmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + recvP = subprocess.Popen(recvcmd, + stdin=sendP.stdout, + stderr=subprocess.PIPE, + close_fds=True) + + recvout,recverr = recvP.communicate() + recverrno = recvP.wait() + sendout,senderr = sendP.communicate() + senderrno = sendP.wait() + + if senderrno != 0: + raise RuntimeError, "Send command: %s failed with exit code" \ + "%d. Error message: \n%s" \ + % (str(sendcmd), senderrno, senderr) + if recverrno != 0: + raise RuntimeError, "Receive command %s failed with exit " \ + "code %d. Error message: \n%s" \ + % (str(recvcmd), recverrno, recverr) + + if prevsnapname != None: + util.debug("Releasing hold on %s" % (prevsnapname), verbose) + snapshot = zfs.Snapshot(prevsnapname) + util.debug("Releasing hold on previous snapshot: %s" \ + % (prevsnapname), + verbose) + snapshot.release(propname) + except Exception, message: + log_error(syslog.LOG_ERR, + "Error during snapshot send/receive operation: %s" \ + % (message)) + + maintenance(pluginfmri) + sys.exit(-1) + + # Finally, after success, make a record of the latest backup + # and release the old snapshot. + ds.set_user_property(propname, snaplabel) + util.debug("Sending of \"%s\"snapshot streams completed" \ + % (snaplabel), + verbose) + +def maintenance(svcfmri): + log_error(syslog.LOG_ERR, + "Placing plugin into maintenance state") + cmd = [smf.SVCADMCMD, "mark", "maintenance", svcfmri] + subprocess.Popen(cmd, close_fds=True) + +def log_error(loglevel, message): + syslog.syslog(loglevel, message + '\n') + sys.stderr.write(message + '\n') + diff --git a/usr/share/time-slider/lib/time_slider/.swp b/usr/share/time-slider/lib/time_slider/.swp new file mode 100644 index 0000000000000000000000000000000000000000..7d10edee5f4fe1f41ae169b0ef8ec9c61facf0f4 GIT binary patch literal 28672 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 0: + self._menu.popup(None, None, + gtk.status_icon_position_menu, + button, time, icon) + + def _dialog_response(self, dialog, response): + dialog.destroy() + + def _notification_closed(self, notifcation): + self._note = None + self._icon.set_blinking(False) + + def _show_notification(self): + if self._icon.is_embedded() == True: + self._note.attach_to_status_icon(self._icon) + self._note.show() + return False + + def _connect_to_object(self): + pass + + def refresh(self): + pass + + def _watch_handler(self, new_owner = None): + if new_owner == None or len(new_owner) == 0: + pass + else: + self._connect_to_object() + + def _setup_icon_for_note(self, themed=None): + if themed: + iconList = themed.get_names() + else: + iconList = ['gnome-dev-harddisk'] + + iconTheme = gtk.icon_theme_get_default() + iconInfo = iconTheme.choose_icon(iconList, 48, 0) + pixbuf = iconInfo.load_icon() + + self._note.set_category("device") + self._note.set_icon_from_pixbuf(pixbuf) + + +class RsyncNote(Note): + + def __init__(self, icon, menu): + Note.__init__(self, icon, menu) + dbus.bus.NameOwnerWatch(bus, + "org.opensolaris.TimeSlider.plugin.rsync", + self._watch_handler) + + self.smfInst = rsyncsmf.RsyncSMF("%s:rsync" \ + % (plugin.PLUGINBASEFMRI)) + self._lock = threading.Lock() + self._masterKey = None + sys,self._nodeName,rel,ver,arch = os.uname() + # References to gio.File and handler_id of a registered + # monitor callback on gio.File + self._fm = None + self._fmID = None + # References to gio.VolumeMonitor and handler_ids of + # registered mount-added and mount-removed callbacks. + self._vm = None + self._vmAdd = None + self._vmRem = None + # Every time the rsync backup script runs it will + # register with d-bus and trigger self._watch_handler(). + # Use this variable to keep track of it's running status. + self._scriptRunning = False + self._targetDirAvail = False + self._syncNowItem = gtk.MenuItem(_("Update Backups Now")) + self._syncNowItem.set_sensitive(False) + self._syncNowItem.connect("activate", + self._sync_now) + self._menu.append(self._syncNowItem) + + self.refresh() + + def _validate_rsync_target(self, path): + """ + Tests path to see if it is the pre-configured + rsync backup device path. + Returns True on success, otherwise False + """ + if not os.path.exists(path): + return False + testDir = join(path, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + testKeyFile = join(path, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + if os.path.exists(testDir) and \ + os.path.exists(testKeyFile): + testKeyVal = None + f = open(testKeyFile, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetKey = val.strip() + break + f.close() + if targetKey == self._masterKey: + return True + return False + + def _setup_monitor(self): + # Disconnect any previously registered signal + # handlers + if self._fm: + self._fm.disconnect(self._fmID) + self._fm = None + + useVolMonitor = False + + # We always compare against masterKey to validate + # an rsync backup device. + self._masterKey = self.smfInst.get_target_key() + self._baseTargetDir = None + online = False + + self._masterTargetDir = self.smfInst.get_target_dir() + + if self._validate_rsync_target(self._masterTargetDir) == True: + self._baseTargetDir = self._masterTargetDir + online = True + + if self._vm == None: + self._vm = gio.volume_monitor_get() + + # If located, see if it's also managed by the volume monitor. + # Or just try to find it otherwise. + mounts = self._vm.get_mounts() + for mount in mounts: + root = mount.get_root() + path = root.get_path() + if self._baseTargetDir != None and \ + path == self._baseTargetDir: + # Means the directory we found is gio monitored, + # so just monitor it using gio.VolumeMonitor. + useVolMonitor = True + break + elif self._validate_rsync_target(path) == True: + # Found it but not where we expected it to be so override + # the target path defined by SMF for now. + useVolMonitor = True + self._baseTargetDir = path + online = True + break + + if self._baseTargetDir == None: + # Means we didn't find it, and we don't know where to expect + # it either - via a hotpluggable device or other nfs/zfs etc. + # We need to hedge our bets and monitor for both. + self._setup_file_monitor(self._masterTargetDir) + self._setup_volume_monitor() + else: + # Found it + if useVolMonitor == True: + # Looks like a removable device. Use gio.VolumeMonitor + # as the preferred monitoring mechanism. + self._setup_volume_monitor() + else: + # Found it on a static mount point like a zfs or nfs + # mount point. + # Can't use gio.VolumeMonitor so use a gio.File monitor + # instead. + self._setup_file_monitor(self._masterTargetDir) + + # Finally, update the UI menu state + self._lock.acquire() + self._targetDirAvail = online + self._update_menu_state() + self._lock.release() + + + def _setup_file_monitor(self, expectedPath): + # Use gio.File monitor as a fallback in + # case gio.VolumeMonitor can't track the device. + # This is the case for static/manual mount points + # such as NFS, ZFS and other non-hotpluggables. + gFile = gio.File(path=expectedPath) + self._fm = gFile.monitor_file(gio.FILE_MONITOR_WATCH_MOUNTS) + self._fmID = self._fm.connect("changed", + self._file_monitor_changed) + + def _setup_volume_monitor(self): + # Check the handler_ids first to see if they have + # already been connected. Avoids multiple callbacks + # for a single event + if self._vmAdd == None: + self._vmAdd = self._vm.connect("mount-added", + self._mount_added) + if self._vmRem == None: + self._vmRem = self._vm.connect("mount-removed", + self._mount_removed) + + def _mount_added(self, monitor, mount): + root = mount.get_root() + path = root.get_path() + if self._validate_rsync_target(path) == True: + # Since gio.VolumeMonitor found the rsync target, don't + # bother relying on gio.File to find it any more. Disconnect + # it's registered callbacks. + if self._fm: + self._fm.disconnect(self._fmID) + self._fm = None + self._lock.acquire() + self._baseTargetDir = path + self._targetDirAvail = True + self._update_menu_state() + self._lock.release() + + def _mount_removed(self, monitor, mount): + root = mount.get_root() + path = root.get_path() + if path == self._baseTargetDir: + self._lock.acquire() + self._targetDirAvail = False + self._update_menu_state() + self._lock.release() + + def _file_monitor_changed(self, filemonitor, file, other_file, event_type): + if file.get_path() == self._masterTargetDir: + self._lock.acquire() + if self._validate_rsync_target(self._masterTargetDir) == True: + self._targetDirAvail = True + else: + self._targetDirAvail = False + self._update_menu_state() + self._lock.release() + + def _update_menu_state(self): + if self._syncNowItem: + if self._targetDirAvail == True and \ + self._scriptRunning == False: + self._syncNowItem.set_sensitive(True) + else: + self._syncNowItem.set_sensitive(False) + + def _watch_handler(self, new_owner = None): + self._lock.acquire() + if new_owner == None or len(new_owner) == 0: + # Script not running or exited + self._scriptRunning = False + else: + self._scriptRunning = True + self._connect_to_object() + self._update_menu_state() + self._lock.release() + + def _rsync_started_handler(self, target, sender=None, interface=None, path=None): + urgency = pynotify.URGENCY_NORMAL + if (self._note != None): + self._note.close() + # Try to pretty things up a bit by displaying volume name + # and hinted icon instead of the raw device path, + # and standard harddisk icon if possible. + icon = None + volume = util.path_to_volume(target) + if volume == None: + volName = target + else: + volName = volume.get_name() + icon = volume.get_icon() + + self._note = pynotify.Notification(_("Backup Started"), + _("Backing up snapshots to:\n%s\n" \ + "Do not disconnect the backup device.") \ + % (volName)) + self._note.connect("closed", \ + self._notification_closed) + self._note.set_urgency(urgency) + self._setup_icon_for_note(icon) + gobject.idle_add(self._show_notification) + + def _rsync_current_handler(self, snapshot, remaining, sender=None, interface=None, path=None): + self._icon.set_tooltip_markup(_("Backing up: \'%s\'\n%d snapshots remaining.\n" \ + "Do not disconnect the backup device.") \ + % (snapshot, remaining)) + + def _rsync_complete_handler(self, target, sender=None, interface=None, path=None): + urgency = pynotify.URGENCY_NORMAL + if (self._note != None): + self._note.close() + # Try to pretty things up a bit by displaying volume name + # and hinted icon instead of the raw device path, + # and standard harddisk icon if possible. + icon = None + volume = util.path_to_volume(target) + if volume == None: + volName = target + else: + volName = volume.get_name() + icon = volume.get_icon() + + self._note = pynotify.Notification(_("Backup Complete"), + _("Your snapshots have been backed up to:\n%s") \ + % (volName)) + self._note.connect("closed", \ + self._notification_closed) + self._note.set_urgency(urgency) + self._setup_icon_for_note(icon) + self._icon.set_has_tooltip(False) + self.queueSize = 0 + gobject.idle_add(self._show_notification) + + def _rsync_synced_handler(self, sender=None, interface=None, path=None): + self._icon.set_tooltip_markup(_("Your backups are up to date.")) + self.queueSize = 0 + + def _rsync_unsynced_handler(self, queueSize, sender=None, interface=None, path=None): + self._icon.set_tooltip_markup(_("%d snapshots are queued for backup.") \ + % (queueSize)) + self.queueSize = queueSize + + def _connect_to_object(self): + try: + remote_object = bus.get_object("org.opensolaris.TimeSlider.plugin.rsync", + "/org/opensolaris/TimeSlider/plugin/rsync") + except dbus.DBusException: + sys.stderr.write("Failed to connect to remote D-Bus object: " + \ + "/org/opensolaris/TimeSlider/plugin/rsync") + return + + # Create an Interface wrapper for the remote object + iface = dbus.Interface(remote_object, "org.opensolaris.TimeSlider.plugin.rsync") + + iface.connect_to_signal("rsync_started", self._rsync_started_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_current", self._rsync_current_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_complete", self._rsync_complete_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_synced", self._rsync_synced_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_unsynced", self._rsync_unsynced_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + + def refresh(self): + # Hide/Unhide rsync menu item based on whether the plugin is online + if self._syncNowItem and \ + self.smfInst.get_service_state() == "online": + #self._setup_file_monitor() + self._setup_monitor() + # Kick start things by initially obtaining the + # backlog size and triggering a callback. + # Signal handlers will keep tooltip status up + # to date afterwards when the backup cron job + # executes. + propName = "%s:rsync" % (backup.propbasename) + queue = backup.list_pending_snapshots(propName) + self.queueSize = len(queue) + if self.queueSize == 0: + self._rsync_synced_handler() + else: + self._rsync_unsynced_handler(self.queueSize) + self._syncNowItem.show() + else: + self._syncNowItem.hide() + + def _sync_now(self, menuItem): + """Runs the rsync-backup script manually + Assumes that user is root since it is only + called from the menu item which is invisible to + not authorised users + """ + cmdPath = os.path.join(os.path.dirname(sys.argv[0]), \ + "time-slider/plugins/rsync/rsync-backup") + if os.geteuid() == 0: + cmd = [cmdPath, \ + "%s:rsync" % (plugin.PLUGINBASEFMRI)] + else: + cmd = ['/usr/bin/gksu' ,cmdPath, \ + "%s:rsync" % (plugin.PLUGINBASEFMRI)] + + subprocess.Popen(cmd, close_fds=True, cwd="/") + + +class CleanupNote(Note): + + def __init__(self, icon, menu): + Note.__init__(self, icon, menu) + self._cleanupHead = None + self._cleanupBody = None + dbus.bus.NameOwnerWatch(bus, + "org.opensolaris.TimeSlider", + self._watch_handler) + + def _show_cleanup_details(self, *args): + # We could keep a dialog around but this a rare + # enough event that's it not worth the effort. + dialog = gtk.MessageDialog(type=gtk.MESSAGE_WARNING, + buttons=gtk.BUTTONS_CLOSE) + dialog.set_title(_("Time Slider: Low Space Warning")) + dialog.set_markup("%s" % (self._cleanupHead)) + dialog.format_secondary_markup(self._cleanupBody) + dialog.show() + dialog.present() + dialog.connect("response", self._dialog_response) + + def _cleanup_handler(self, pool, severity, threshhold, sender=None, interface=None, path=None): + if severity == 4: + expiry = pynotify.EXPIRES_NEVER + urgency = pynotify.URGENCY_CRITICAL + self._cleanupHead = _("Emergency: \'%s\' is full!") % pool + notifyBody = _("The file system: \'%s\', is over %s%% full.") \ + % (pool, threshhold) + self._cleanupBody = _("The file system: \'%s\', is over %s%% full.\n" + "As an emergency measure, Time Slider has " + "destroyed all of its backups.\nTo fix this problem, " + "delete any unnecessary files on \'%s\', or add " + "disk space (see ZFS documentation).") \ + % (pool, threshhold, pool) + elif severity == 3: + expiry = pynotify.EXPIRES_NEVER + urgency = pynotify.URGENCY_CRITICAL + self._cleanupHead = _("Emergency: \'%s\' is almost full!") % pool + notifyBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity") \ + % (pool, threshhold) + self._cleanupBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity. As an emerency measure, " + "Time Slider has has destroyed most or all of its " + "backups to prevent the disk becoming full. " + "To prevent this from happening again, delete " + "any unnecessary files on \'%s\', or add disk " + "space (see ZFS documentation).") \ + % (pool, threshhold, pool) + elif severity == 2: + expiry = pynotify.EXPIRES_NEVER + urgency = pynotify.URGENCY_CRITICAL + self._cleanupHead = _("Urgent: \'%s\' is almost full!") % pool + notifyBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity") \ + % (pool, threshhold) + self._cleanupBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity. As a remedial measure, " + "Time Slider has destroyed some backups, and will " + "destroy more, eventually all, as capacity continues " + "to diminish.\nTo prevent this from happening again, " + "delete any unnecessary files on \'%s\', or add disk " + "space (see ZFS documentation).") \ + % (pool, threshhold, pool) + elif severity == 1: + expiry = 20000 # 20 seconds + urgency = pynotify.URGENCY_NORMAL + self._cleanupHead = _("Warning: \'%s\' is getting full") % pool + notifyBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity") \ + % (pool, threshhold) + self._cleanupBody = _("\'%s\' exceeded %s%% of its total " + "capacity. To fix this, Time Slider has destroyed " + "some recent backups, and will destroy more as " + "capacity continues to diminish.\nTo prevent " + "this from happening again, delete any " + "unnecessary files on \'%s\', or add disk space " + "(see ZFS documentation).\n") \ + % (pool, threshhold, pool) + else: + return # No other values currently supported + + if (self._note != None): + self._note.close() + self._note = pynotify.Notification(self._cleanupHead, + notifyBody) + self._note.add_action("clicked", + _("Details..."), + self._show_cleanup_details) + self._note.connect("closed", + self._notification_closed) + self._note.set_urgency(urgency) + self._note.set_timeout(expiry) + self._setup_icon_for_note() + self._icon.set_blinking(True) + gobject.idle_add(self._show_notification) + + def _connect_to_object(self): + try: + remote_object = bus.get_object("org.opensolaris.TimeSlider", + "/org/opensolaris/TimeSlider/autosnap") + except dbus.DBusException: + sys.stderr.write("Failed to connect to remote D-Bus object: " + \ + "/org/opensolaris/TimeSlider/autosnap") + + #Create an Interface wrapper for the remote object + iface = dbus.Interface(remote_object, "org.opensolaris.TimeSlider.autosnap") + + iface.connect_to_signal("capacity_exceeded", self._cleanup_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + + + +class SetupNote(Note): + + def __init__(self, icon, menu, manager): + Note.__init__(self, icon, menu) + # We are passed a reference to out parent so we can + # provide it notification which it can then circulate + # to other notification objects such as Rsync and + # Cleanup + self._manager = manager + self._icon = icon + self._menu = menu + self._configSvcItem = gtk.MenuItem(_("Configure Time Slider...")) + self._configSvcItem.connect("activate", + self._run_config_app) + self._configSvcItem.set_sensitive(True) + self._menu.append(self._configSvcItem) + self._configSvcItem.show() + dbus.bus.NameOwnerWatch(bus, + "org.opensolaris.TimeSlider.config", + self._watch_handler) + + def _connect_to_object(self): + try: + remote_object = bus.get_object("org.opensolaris.TimeSlider.config", + "/org/opensolaris/TimeSlider/config") + except dbus.DBusException: + sys.stderr.write("Failed to connect to remote D-Bus object: " + \ + "/org/opensolaris/TimeSlider/config") + + #Create an Interface wrapper for the remote object + iface = dbus.Interface(remote_object, "org.opensolaris.TimeSlider.config") + + iface.connect_to_signal("config_changed", self._config_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + + def _config_handler(self, sender=None, interface=None, path=None): + # Notify the manager. + # This will eventually propogate through to an invocation + # of our own refresh() method. + self._manager.refresh() + + def _run_config_app(self, menuItem): + cmdPath = os.path.join(os.path.dirname(sys.argv[0]), + os.path.pardir, + "bin", + "time-slider-setup") + cmd = os.path.abspath(cmdPath) + # The setup GUI deals with it's own security and + # authorisation, so no need to pfexec it. Any + # changes made to configuration will come back to + # us by way of D-Bus notification. + subprocess.Popen(cmd, close_fds=True) + +class NoteManager(): + def __init__(self): + # Notification objects need to share a common + # status icon and popup menu so these are created + # outside the object and passed to the constructor + self._menu = gtk.Menu() + self._icon = gtk.StatusIcon() + self._icon.set_from_icon_name("time-slider-setup") + self._setupNote = SetupNote(self._icon, + self._menu, + self) + self._cleanupNote = CleanupNote(self._icon, + self._menu) + self._rsyncNote = RsyncNote(self._icon, + self._menu) + + def refresh(self): + self._rsyncNote.refresh() + +bus = dbus.SystemBus() + +def main(argv): + mainloop = gobject.MainLoop() + dbus.mainloop.glib.DBusGMainLoop(set_as_default = True) + gobject.threads_init() + pynotify.init(_("Time Slider")) + + noteManager = NoteManager() + + try: + mainloop.run() + except: + print "Exiting" + +if __name__ == '__main__': + main() + diff --git a/usr/share/time-slider/lib/time_slider/applet.py~ b/usr/share/time-slider/lib/time_slider/applet.py~ new file mode 100755 index 0000000..7d722a6 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/applet.py~ @@ -0,0 +1,645 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +import os +import subprocess +import threading +import gobject +import dbus +import dbus.decorators +import dbus.glib +import dbus.mainloop +import dbus.mainloop.glib +import gio +import gtk +import pygtk +import pynotify + +from time_slider import util, rbac + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import backup, rsyncsmf + +class Note: + _iconConnected = False + + def __init__(self, icon, menu): + self._note = None + self._msgDialog = None + self._menu = menu + self._icon = icon + if Note._iconConnected == False: + self._icon.connect("popup-menu", self._popup_menu) + Note._iconConnected = True + self._icon.set_visible(True) + + def _popup_menu(self, icon, button, time): + if button == 3: + # Don't popup an empty menu + if len(self._menu.get_children()) > 0: + self._menu.popup(None, None, + gtk.status_icon_position_menu, + button, time, icon) + + def _dialog_response(self, dialog, response): + dialog.destroy() + + def _notification_closed(self, notifcation): + self._note = None + self._icon.set_blinking(False) + + def _show_notification(self): + if self._icon.is_embedded() == True: + self._note.attach_to_status_icon(self._icon) + self._note.show() + return False + + def _connect_to_object(self): + pass + + def refresh(self): + pass + + def _watch_handler(self, new_owner = None): + if new_owner == None or len(new_owner) == 0: + pass + else: + self._connect_to_object() + + def _setup_icon_for_note(self, themed=None): + if themed: + iconList = themed.get_names() + else: + iconList = ['gnome-dev-harddisk'] + + iconTheme = gtk.icon_theme_get_default() + iconInfo = iconTheme.choose_icon(iconList, 48, 0) + pixbuf = iconInfo.load_icon() + + self._note.set_category("device") + self._note.set_icon_from_pixbuf(pixbuf) + + +class RsyncNote(Note): + + def __init__(self, icon, menu): + Note.__init__(self, icon, menu) + dbus.bus.NameOwnerWatch(bus, + "org.opensolaris.TimeSlider.plugin.rsync", + self._watch_handler) + + self.smfInst = rsyncsmf.RsyncSMF("%s:rsync" \ + % (plugin.PLUGINBASEFMRI)) + self._lock = threading.Lock() + self._masterKey = None + sys,self._nodeName,rel,ver,arch = os.uname() + # References to gio.File and handler_id of a registered + # monitor callback on gio.File + self._fm = None + self._fmID = None + # References to gio.VolumeMonitor and handler_ids of + # registered mount-added and mount-removed callbacks. + self._vm = None + self._vmAdd = None + self._vmRem = None + # Every time the rsync backup script runs it will + # register with d-bus and trigger self._watch_handler(). + # Use this variable to keep track of it's running status. + self._scriptRunning = False + self._targetDirAvail = False + self._syncNowItem = gtk.MenuItem(_("Update Backups Now")) + self._syncNowItem.set_sensitive(False) + self._syncNowItem.connect("activate", + self._sync_now) + self._menu.append(self._syncNowItem) + + self.refresh() + + def _validate_rsync_target(self, path): + """ + Tests path to see if it is the pre-configured + rsync backup device path. + Returns True on success, otherwise False + """ + if not os.path.exists(path): + return False + testDir = join(path, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + testKeyFile = join(path, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + if os.path.exists(testDir) and \ + os.path.exists(testKeyFile): + testKeyVal = None + f = open(testKeyFile, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetKey = val.strip() + break + f.close() + if targetKey == self._masterKey: + return True + return False + + def _setup_monitor(self): + # Disconnect any previously registered signal + # handlers + if self._fm: + self._fm.disconnect(self._fmID) + self._fm = None + + useVolMonitor = False + + # We always compare against masterKey to validate + # an rsync backup device. + self._masterKey = self.smfInst.get_target_key() + self._baseTargetDir = None + online = False + + self._masterTargetDir = self.smfInst.get_target_dir() + + if self._validate_rsync_target(self._masterTargetDir) == True: + self._baseTargetDir = self._masterTargetDir + online = True + + if self._vm == None: + self._vm = gio.volume_monitor_get() + + # If located, see if it's also managed by the volume monitor. + # Or just try to find it otherwise. + mounts = self._vm.get_mounts() + for mount in mounts: + root = mount.get_root() + path = root.get_path() + if self._baseTargetDir != None and \ + path == self._baseTargetDir: + # Means the directory we found is gio monitored, + # so just monitor it using gio.VolumeMonitor. + useVolMonitor = True + break + elif self._validate_rsync_target(path) == True: + # Found it but not where we expected it to be so override + # the target path defined by SMF for now. + useVolMonitor = True + self._baseTargetDir = path + online = True + break + + if self._baseTargetDir == None: + # Means we didn't find it, and we don't know where to expect + # it either - via a hotpluggable device or other nfs/zfs etc. + # We need to hedge our bets and monitor for both. + self._setup_file_monitor(self._masterTargetDir) + self._setup_volume_monitor() + else: + # Found it + if useVolMonitor == True: + # Looks like a removable device. Use gio.VolumeMonitor + # as the preferred monitoring mechanism. + self._setup_volume_monitor() + else: + # Found it on a static mount point like a zfs or nfs + # mount point. + # Can't use gio.VolumeMonitor so use a gio.File monitor + # instead. + self._setup_file_monitor(self._masterTargetDir) + + # Finally, update the UI menu state + self._lock.acquire() + self._targetDirAvail = online + self._update_menu_state() + self._lock.release() + + + def _setup_file_monitor(self, expectedPath): + # Use gio.File monitor as a fallback in + # case gio.VolumeMonitor can't track the device. + # This is the case for static/manual mount points + # such as NFS, ZFS and other non-hotpluggables. + gFile = gio.File(path=expectedPath) + self._fm = gFile.monitor_file(gio.FILE_MONITOR_WATCH_MOUNTS) + self._fmID = self._fm.connect("changed", + self._file_monitor_changed) + + def _setup_volume_monitor(self): + # Check the handler_ids first to see if they have + # already been connected. Avoids multiple callbacks + # for a single event + if self._vmAdd == None: + self._vmAdd = self._vm.connect("mount-added", + self._mount_added) + if self._vmRem == None: + self._vmRem = self._vm.connect("mount-removed", + self._mount_removed) + + def _mount_added(self, monitor, mount): + root = mount.get_root() + path = root.get_path() + if self._validate_rsync_target(path) == True: + # Since gio.VolumeMonitor found the rsync target, don't + # bother relying on gio.File to find it any more. Disconnect + # it's registered callbacks. + if self._fm: + self._fm.disconnect(self._fmID) + self._fm = None + self._lock.acquire() + self._baseTargetDir = path + self._targetDirAvail = True + self._update_menu_state() + self._lock.release() + + def _mount_removed(self, monitor, mount): + root = mount.get_root() + path = root.get_path() + if path == self._baseTargetDir: + self._lock.acquire() + self._targetDirAvail = False + self._update_menu_state() + self._lock.release() + + def _file_monitor_changed(self, filemonitor, file, other_file, event_type): + if file.get_path() == self._masterTargetDir: + self._lock.acquire() + if self._validate_rsync_target(self._masterTargetDir) == True: + self._targetDirAvail = True + else: + self._targetDirAvail = False + self._update_menu_state() + self._lock.release() + + def _update_menu_state(self): + if self._syncNowItem: + if self._targetDirAvail == True and \ + self._scriptRunning == False: + self._syncNowItem.set_sensitive(True) + else: + self._syncNowItem.set_sensitive(False) + + def _watch_handler(self, new_owner = None): + self._lock.acquire() + if new_owner == None or len(new_owner) == 0: + # Script not running or exited + self._scriptRunning = False + else: + self._scriptRunning = True + self._connect_to_object() + self._update_menu_state() + self._lock.release() + + def _rsync_started_handler(self, target, sender=None, interface=None, path=None): + urgency = pynotify.URGENCY_NORMAL + if (self._note != None): + self._note.close() + # Try to pretty things up a bit by displaying volume name + # and hinted icon instead of the raw device path, + # and standard harddisk icon if possible. + icon = None + volume = util.path_to_volume(target) + if volume == None: + volName = target + else: + volName = volume.get_name() + icon = volume.get_icon() + + self._note = pynotify.Notification(_("Backup Started"), + _("Backing up snapshots to:\n%s\n" \ + "Do not disconnect the backup device.") \ + % (volName)) + self._note.connect("closed", \ + self._notification_closed) + self._note.set_urgency(urgency) + self._setup_icon_for_note(icon) + gobject.idle_add(self._show_notification) + + def _rsync_current_handler(self, snapshot, remaining, sender=None, interface=None, path=None): + self._icon.set_tooltip_markup(_("Backing up: \'%s\'\n%d snapshots remaining.\n" \ + "Do not disconnect the backup device.") \ + % (snapshot, remaining)) + + def _rsync_complete_handler(self, target, sender=None, interface=None, path=None): + urgency = pynotify.URGENCY_NORMAL + if (self._note != None): + self._note.close() + # Try to pretty things up a bit by displaying volume name + # and hinted icon instead of the raw device path, + # and standard harddisk icon if possible. + icon = None + volume = util.path_to_volume(target) + if volume == None: + volName = target + else: + volName = volume.get_name() + icon = volume.get_icon() + + self._note = pynotify.Notification(_("Backup Complete"), + _("Your snapshots have been backed up to:\n%s") \ + % (volName)) + self._note.connect("closed", \ + self._notification_closed) + self._note.set_urgency(urgency) + self._setup_icon_for_note(icon) + self._icon.set_has_tooltip(False) + self.queueSize = 0 + gobject.idle_add(self._show_notification) + + def _rsync_synced_handler(self, sender=None, interface=None, path=None): + self._icon.set_tooltip_markup(_("Your backups are up to date.")) + self.queueSize = 0 + + def _rsync_unsynced_handler(self, queueSize, sender=None, interface=None, path=None): + self._icon.set_tooltip_markup(_("%d snapshots are queued for backup.") \ + % (queueSize)) + self.queueSize = queueSize + + def _connect_to_object(self): + try: + remote_object = bus.get_object("org.opensolaris.TimeSlider.plugin.rsync", + "/org/opensolaris/TimeSlider/plugin/rsync") + except dbus.DBusException: + sys.stderr.write("Failed to connect to remote D-Bus object: " + \ + "/org/opensolaris/TimeSlider/plugin/rsync") + return + + # Create an Interface wrapper for the remote object + iface = dbus.Interface(remote_object, "org.opensolaris.TimeSlider.plugin.rsync") + + iface.connect_to_signal("rsync_started", self._rsync_started_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_current", self._rsync_current_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_complete", self._rsync_complete_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_synced", self._rsync_synced_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + iface.connect_to_signal("rsync_unsynced", self._rsync_unsynced_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + + def refresh(self): + # Hide/Unhide rsync menu item based on whether the plugin is online + if self._syncNowItem and \ + self.smfInst.get_service_state() == "online": + #self._setup_file_monitor() + self._setup_monitor() + # Kick start things by initially obtaining the + # backlog size and triggering a callback. + # Signal handlers will keep tooltip status up + # to date afterwards when the backup cron job + # executes. + propName = "%s:rsync" % (backup.propbasename) + queue = backup.list_pending_snapshots(propName) + self.queueSize = len(queue) + if self.queueSize == 0: + self._rsync_synced_handler() + else: + self._rsync_unsynced_handler(self.queueSize) + self._syncNowItem.show() + else: + self._syncNowItem.hide() + + def _sync_now(self, menuItem): + """Runs the rsync-backup script manually + Assumes that user is root since it is only + called from the menu item which is invisible to + not authorised users + """ + cmdPath = os.path.join(os.path.dirname(sys.argv[0]), \ + "time-slider/plugins/rsync/rsync-backup") + if os.geteuid() == 0: + cmd = [cmdPath, \ + "%s:rsync" % (plugin.PLUGINBASEFMRI)] + else: + cmd = ['/usr/bin/gksu' ,cmdPath, \ + "%s:rsync" % (plugin.PLUGINBASEFMRI)] + + subprocess.Popen(cmd, close_fds=True, cwd="/") + + +class CleanupNote(Note): + + def __init__(self, icon, menu): + Note.__init__(self, icon, menu) + self._cleanupHead = None + self._cleanupBody = None + dbus.bus.NameOwnerWatch(bus, + "org.opensolaris.TimeSlider", + self._watch_handler) + + def _show_cleanup_details(self, *args): + # We could keep a dialog around but this a rare + # enough event that's it not worth the effort. + dialog = gtk.MessageDialog(type=gtk.MESSAGE_WARNING, + buttons=gtk.BUTTONS_CLOSE) + dialog.set_title(_("Time Slider: Low Space Warning")) + dialog.set_markup("%s" % (self._cleanupHead)) + dialog.format_secondary_markup(self._cleanupBody) + dialog.show() + dialog.present() + dialog.connect("response", self._dialog_response) + + def _cleanup_handler(self, pool, severity, threshhold, sender=None, interface=None, path=None): + if severity == 4: + expiry = pynotify.EXPIRES_NEVER + urgency = pynotify.URGENCY_CRITICAL + self._cleanupHead = _("Emergency: \'%s\' is full!") % pool + notifyBody = _("The file system: \'%s\', is over %s%% full.") \ + % (pool, threshhold) + self._cleanupBody = _("The file system: \'%s\', is over %s%% full.\n" + "As an emergency measure, Time Slider has " + "destroyed all of its backups.\nTo fix this problem, " + "delete any unnecessary files on \'%s\', or add " + "disk space (see ZFS documentation).") \ + % (pool, threshhold, pool) + elif severity == 3: + expiry = pynotify.EXPIRES_NEVER + urgency = pynotify.URGENCY_CRITICAL + self._cleanupHead = _("Emergency: \'%s\' is almost full!") % pool + notifyBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity") \ + % (pool, threshhold) + self._cleanupBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity. As an emerency measure, " + "Time Slider has has destroyed most or all of its " + "backups to prevent the disk becoming full. " + "To prevent this from happening again, delete " + "any unnecessary files on \'%s\', or add disk " + "space (see ZFS documentation).") \ + % (pool, threshhold, pool) + elif severity == 2: + expiry = pynotify.EXPIRES_NEVER + urgency = pynotify.URGENCY_CRITICAL + self._cleanupHead = _("Urgent: \'%s\' is almost full!") % pool + notifyBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity") \ + % (pool, threshhold) + self._cleanupBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity. As a remedial measure, " + "Time Slider has destroyed some backups, and will " + "destroy more, eventually all, as capacity continues " + "to diminish.\nTo prevent this from happening again, " + "delete any unnecessary files on \'%s\', or add disk " + "space (see ZFS documentation).") \ + % (pool, threshhold, pool) + elif severity == 1: + expiry = 20000 # 20 seconds + urgency = pynotify.URGENCY_NORMAL + self._cleanupHead = _("Warning: \'%s\' is getting full") % pool + notifyBody = _("The file system: \'%s\', exceeded %s%% " + "of its total capacity") \ + % (pool, threshhold) + self._cleanupBody = _("\'%s\' exceeded %s%% of its total " + "capacity. To fix this, Time Slider has destroyed " + "some recent backups, and will destroy more as " + "capacity continues to diminish.\nTo prevent " + "this from happening again, delete any " + "unnecessary files on \'%s\', or add disk space " + "(see ZFS documentation).\n") \ + % (pool, threshhold, pool) + else: + return # No other values currently supported + + if (self._note != None): + self._note.close() + self._note = pynotify.Notification(self._cleanupHead, + notifyBody) + self._note.add_action("clicked", + _("Details..."), + self._show_cleanup_details) + self._note.connect("closed", + self._notification_closed) + self._note.set_urgency(urgency) + self._note.set_timeout(expiry) + self._setup_icon_for_note() + self._icon.set_blinking(True) + gobject.idle_add(self._show_notification) + + def _connect_to_object(self): + try: + remote_object = bus.get_object("org.opensolaris.TimeSlider", + "/org/opensolaris/TimeSlider/autosnap") + except dbus.DBusException: + sys.stderr.write("Failed to connect to remote D-Bus object: " + \ + "/org/opensolaris/TimeSlider/autosnap") + + #Create an Interface wrapper for the remote object + iface = dbus.Interface(remote_object, "org.opensolaris.TimeSlider.autosnap") + + iface.connect_to_signal("capacity_exceeded", self._cleanup_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + + + +class SetupNote(Note): + + def __init__(self, icon, menu, manager): + Note.__init__(self, icon, menu) + # We are passed a reference to out parent so we can + # provide it notification which it can then circulate + # to other notification objects such as Rsync and + # Cleanup + self._manager = manager + self._icon = icon + self._menu = menu + self._configSvcItem = gtk.MenuItem(_("Configure Time Slider...")) + self._configSvcItem.connect("activate", + self._run_config_app) + self._configSvcItem.set_sensitive(True) + self._menu.append(self._configSvcItem) + self._configSvcItem.show() + dbus.bus.NameOwnerWatch(bus, + "org.opensolaris.TimeSlider.config", + self._watch_handler) + + def _connect_to_object(self): + try: + remote_object = bus.get_object("org.opensolaris.TimeSlider.config", + "/org/opensolaris/TimeSlider/config") + except dbus.DBusException: + sys.stderr.write("Failed to connect to remote D-Bus object: " + \ + "/org/opensolaris/TimeSlider/config") + + #Create an Interface wrapper for the remote object + iface = dbus.Interface(remote_object, "org.opensolaris.TimeSlider.config") + + iface.connect_to_signal("config_changed", self._config_handler, sender_keyword='sender', + interface_keyword='interface', path_keyword='path') + + def _config_handler(self, sender=None, interface=None, path=None): + # Notify the manager. + # This will eventually propogate through to an invocation + # of our own refresh() method. + self._manager.refresh() + + def _run_config_app(self, menuItem): + cmdPath = os.path.join(os.path.dirname(sys.argv[0]), + os.path.pardir, + "bin", + "time-slider-setup") + cmd = os.path.abspath(cmdPath) + # The setup GUI deals with it's own security and + # authorisation, so no need to pfexec it. Any + # changes made to configuration will come back to + # us by way of D-Bus notification. + subprocess.Popen(cmd, close_fds=True) + +class NoteManager(): + def __init__(self): + # Notification objects need to share a common + # status icon and popup menu so these are created + # outside the object and passed to the constructor + self._menu = gtk.Menu() + self._icon = gtk.StatusIcon() + self._icon.set_from_icon_name("time-slider-setup") + self._setupNote = SetupNote(self._icon, + self._menu, + self) + self._cleanupNote = CleanupNote(self._icon, + self._menu) + self._rsyncNote = RsyncNote(self._icon, + self._menu) + + def refresh(self): + self._rsyncNote.refresh() + +bus = dbus.SystemBus() + +def main(argv): + mainloop = gobject.MainLoop() + dbus.mainloop.glib.DBusGMainLoop(set_as_default = True) + gobject.threads_init() + pynotify.init(_("Time Slider")) + + noteManager = NoteManager() + + try: + mainloop.run() + except: + print "Exiting" + +if __name__ == '__main__': + main() + diff --git a/usr/share/time-slider/lib/time_slider/autosnapsmf.py b/usr/share/time-slider/lib/time_slider/autosnapsmf.py new file mode 100755 index 0000000..e659a28 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/autosnapsmf.py @@ -0,0 +1,170 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import threading +import smf +import util + +factoryDefaultSchedules = ("monthly", "weekly", "daily", "hourly", "frequent") + +BASESVC= "svc:/system/filesystem/zfs/auto-snapshot" +SNAPLABELPREFIX = "zfs-auto-snap" +ZFSPROPGROUP = "zfs" + + +# Bombarding the class with schedule queries causes the occasional +# OSError exception due to interrupted system calls. +# Serialising them helps prevent this unlikely event from occuring. +_scheddetaillock = threading.RLock() + +class AutoSnap(smf.SMFInstance): + + def __init__(self, schedule): + smf.SMFInstance.__init__(self, "%s:%s" % (BASESVC, schedule)) + self.schedule = schedule + + def get_schedule_details(self): + svc= "%s:%s" % (BASESVC, self.schedule) + _scheddetaillock.acquire() + try: + interval = self.get_prop(ZFSPROPGROUP, "interval") + period = int(self.get_prop(ZFSPROPGROUP, "period")) + keep = int(self.get_prop(ZFSPROPGROUP, "keep")) + + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + finally: + _scheddetaillock.release() + + return [self.schedule, interval, period, keep] + +# FIXME - merge with enable_default_schedules() +def disable_default_schedules(): + """ + Disables the default auto-snapshot SMF instances corresponding + to: "frequent", "hourly", "daily", "weekly" and "monthly" + schedules + Raises RuntimeError exception if unsuccessful + """ + + for s in factoryDefaultSchedules: + # Acquire the scheddetail lock since their status will + # likely be changed as a result of enabling the instances. + _scheddetaillock.acquire() + instanceName = "%s:%s" % (BASESVC,s) + svc = smf.SMFInstance(instanceName) + svc.disable_service() + _scheddetaillock.release() + +def enable_default_schedules(): + """ + Enables the default auto-snapshot SMF instances corresponding + to: "frequent", "hourly", "daily", "weekly" and "monthly" + schedules + Raises RuntimeError exception if unsuccessful + """ + for s in factoryDefaultSchedules: + # Acquire the scheddetail lock since their status will + # likely be changed as a result of enabling the instances. + _scheddetaillock.acquire() + instanceName = "%s:%s" % (BASESVC,s) + svc = smf.SMFInstance(instanceName) + svc.enable_service() + _scheddetaillock.release() + +def get_default_schedules(): + """ + Finds the default schedules that are enabled (online, offline or degraded) + """ + #This is not the fastest method but it is the safest, we need + #to ensure that default schedules are processed in the pre-defined + #order to ensure that the overlap between them is adhered to + #correctly. monthly->weekly->daily->hourly->frequent. They have + #to be processed first and they HAVE to be in the correct order. + _defaultSchedules = [] + for s in factoryDefaultSchedules: + instanceName = "%s:%s" % (BASESVC,s) + cmd = [smf.SVCSCMD, "-H", "-o", "state", instanceName] + _scheddetaillock.acquire() + try: + outdata,errdata = util.run_command(cmd) + finally: + _scheddetaillock.release() + result = outdata.rstrip() + # Note that the schedules, being dependent on the time-slider service + # itself will typically be in an offline state when enabled. They will + # transition to an "online" state once time-slider itself comes + # "online" to satisfy it's dependency + if result == "online" or result == "offline" or result == "degraded": + instance = AutoSnap(s) + try: + _defaultSchedules.append(instance.get_schedule_details()) + except RuntimeError, message: + raise RuntimeError, "Error getting schedule details for " + \ + "default auto-snapshot SMF instance:" + \ + "\n\t" + instanceName + "\nDetails:\n" + \ + str(message) + return _defaultSchedules + +def get_custom_schedules(): + """ + Finds custom schedules ie. not the factory default + 'monthly', 'weekly', 'hourly', 'daily' and 'frequent' schedules + """ + _customSchedules = [] + cmd = [smf.SVCSCMD, "-H", "-o", "state,FMRI", BASESVC] + _scheddetaillock.acquire() + try: + outdata,errdata = util.run_command(cmd) + finally: + _scheddetaillock.release() + + for line in outdata.rstrip().split('\n'): + line = line.rstrip().split() + state = line[0] + fmri = line[1] + fmri = fmri.rsplit(":", 1) + label = fmri[1] + if label not in factoryDefaultSchedules: + # Note that the schedules, being dependent on the time-slider service + # itself will typically be in an offline state when enabled. They will + # transition to an "online" state once time-slider itself comes + # "online" to satisfy it's dependency + if state == "online" or state == "offline" or state == "degraded": + instance = AutoSnap(label) + try: + _customSchedules.append(instance.get_schedule_details()) + except RuntimeError, message: + raise RuntimeError, "Error getting schedule details " + \ + "for custom auto-snapshot SMF " + \ + "instance:\n\t" + label + "\n" + \ + "Details:\n" + str(message) + return _customSchedules + + +if __name__ == "__main__": + defaults = get_default_schedules() + for sched in defaults: + S = AutoSnap(sched[0]) + print S.get_schedule_details() + diff --git a/usr/share/time-slider/lib/time_slider/dbussvc.py b/usr/share/time-slider/lib/time_slider/dbussvc.py new file mode 100644 index 0000000..e9f80b0 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/dbussvc.py @@ -0,0 +1,103 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + + +import dbus +import dbus.service +import dbus.mainloop +import dbus.mainloop.glib + + +class AutoSnap(dbus.service.Object): + """ + D-Bus object for Time Slider's auto snapshot features. + """ + def __init__(self, bus, path, snapshotmanager): + self.snapshotmanager = snapshotmanager + self._bus = bus + dbus.service.Object.__init__(self, + bus, + path) + + # Remedial cleanup signal + @dbus.service.signal(dbus_interface="org.opensolaris.TimeSlider.autosnap", + signature='suu') + def capacity_exceeded(self, pool, severity, threshhold): + pass + +class RsyncBackup(dbus.service.Object): + """ + D-Bus object for Time Slider's rsync backup feature. + """ + def __init__(self, bus, path): + self._bus = bus + dbus.service.Object.__init__(self, + bus, + path) + + # Rsync operation rsync_started signal + @dbus.service.signal(dbus_interface="org.opensolaris.TimeSlider.plugin.rsync", + signature='s') + def rsync_started(self, target): + pass + + # Rsync operation rsync_current signal + @dbus.service.signal(dbus_interface="org.opensolaris.TimeSlider.plugin.rsync", + signature='su') + def rsync_current(self, snapshot, remaining): + pass + + # Rsync operation rsync_complete signal + @dbus.service.signal(dbus_interface="org.opensolaris.TimeSlider.plugin.rsync", + signature='s') + def rsync_complete(self, target): + pass + + # Rsync operation rsync_synced signal + @dbus.service.signal(dbus_interface="org.opensolaris.TimeSlider.plugin.rsync", + signature='') + def rsync_synced(self): + pass + + # Rsync operation rsync_unsynced signal + @dbus.service.signal(dbus_interface="org.opensolaris.TimeSlider.plugin.rsync", + signature='u') + def rsync_unsynced(self, queueSize): + pass + + +class Config(dbus.service.Object): + """ + D-Bus object representing Time Slider service configuration changes. + """ + def __init__(self, bus, path): + self._bus = bus + dbus.service.Object.__init__(self, + bus, + path) + # Service configuration change signal. Nothing fancy for now. + # Listeners need to figure out what changed for themselves. + @dbus.service.signal(dbus_interface="org.opensolaris.TimeSlider.config", + signature='') + def config_changed(self): + pass + diff --git a/usr/share/time-slider/lib/time_slider/deletegui.py b/usr/share/time-slider/lib/time_slider/deletegui.py new file mode 100755 index 0000000..8023d9a --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/deletegui.py @@ -0,0 +1,756 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import threading +import sys +import os +import time +import getopt +import locale +import shutil +import fcntl +from bisect import insort + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import glib + import gobject +except: + sys.exit(1) + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import rsyncsmf + + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from rbac import RBACprofile + +class RsyncBackup: + + def __init__(self, mountpoint, rsync_dir = None, fsname= None, snaplabel= None, creationtime= None): + + if rsync_dir == None: + self.__init_from_mp (mountpoint) + else: + self.rsync_dir = rsync_dir + self.mountpoint = mountpoint + self.fsname = fsname + self.snaplabel = snaplabel + + self.creationtime = creationtime + try: + tm = time.localtime(self.creationtime) + self.creationtime_str = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + self.creationtime_str = time.ctime(self.creationtime) + fs = zfs.Filesystem (self.fsname) + self.zfs_mountpoint = fs.get_mountpoint () + + def __init_from_mp (self, mountpoint): + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsync_dir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + self.mountpoint = mountpoint + + s1 = mountpoint.split ("%s/" % self.rsync_dir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + s3 = s2[1].split ('/',2) + self.fsname = s2[0] + self.snaplabel = s3[1] + self.creationtime = os.stat(mountpoint).st_mtime + + def __str__(self): + ret = "self.rsync_dir = %s\n \ + self.mountpoint = %s\n \ + self.fsname = %s\n \ + self.snaplabel = %s\n" % (self.rsync_dir, + self.mountpoint, self.fsname, + self.snaplabel) + return ret + + + def exists(self): + return os.path.exists(self.mountpoint) + + def destroy(self): + lockFileDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOCKSUFFIX) + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + lockFile = os.path.join(lockFileDir, self.snaplabel + ".lock") + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + raise RuntimeError, \ + "couldn't delete %s, already used by another process" % self.mountpoint + return + + trashDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX) + if not os.path.exists(trashDir): + os.makedirs(trashDir, 0755) + + backupTrashDir = os.path.join (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX, + self.snaplabel) + + # move then delete + os.rename (self.mountpoint, backupTrashDir) + shutil.rmtree (backupTrashDir) + + log = "%s/%s/%s/%s.log" % (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOGSUFFIX, + self.snaplabel) + if os.path.exists (log): + os.unlink (log) + + lockFp.close() + os.unlink(lockFile) + +class DeleteSnapManager: + + def __init__(self, snapshots = None): + self.xml = gtk.glade.XML("%s/../../glade/time-slider-delete.glade" \ + % (os.path.dirname(__file__))) + self.backuptodelete = [] + self.shortcircuit = [] + maindialog = self.xml.get_widget("time-slider-delete") + self.pulsedialog = self.xml.get_widget("pulsedialog") + self.pulsedialog.set_transient_for(maindialog) + self.datasets = zfs.Datasets() + if snapshots: + maindialog.hide() + self.shortcircuit = snapshots + else: + glib.idle_add(self.__init_scan) + + self.progressdialog = self.xml.get_widget("deletingdialog") + self.progressdialog.set_transient_for(maindialog) + self.progressbar = self.xml.get_widget("deletingprogress") + # signal dictionary + dic = {"on_closebutton_clicked" : gtk.main_quit, + "on_window_delete_event" : gtk.main_quit, + "on_snapshotmanager_delete_event" : gtk.main_quit, + "on_fsfilterentry_changed" : self.__on_filterentry_changed, + "on_schedfilterentry_changed" : self.__on_filterentry_changed, + "on_typefiltercombo_changed" : self.__on_filterentry_changed, + "on_selectbutton_clicked" : self.__on_selectbutton_clicked, + "on_deselectbutton_clicked" : self.__on_deselectbutton_clicked, + "on_deletebutton_clicked" : self.__on_deletebutton_clicked, + "on_confirmcancel_clicked" : self.__on_confirmcancel_clicked, + "on_confirmdelete_clicked" : self.__on_confirmdelete_clicked, + "on_errordialog_response" : self.__on_errordialog_response} + self.xml.signal_autoconnect(dic) + + def initialise_view(self): + if len(self.shortcircuit) == 0: + # Set TreeViews + self.liststorefs = gtk.ListStore(str, str, str, str, str, long, + gobject.TYPE_PYOBJECT) + list_filter = self.liststorefs.filter_new() + list_sort = gtk.TreeModelSort(list_filter) + list_sort.set_sort_column_id(1, gtk.SORT_ASCENDING) + + self.snaptreeview = self.xml.get_widget("snaplist") + self.snaptreeview.set_model(self.liststorefs) + self.snaptreeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + + cell0 = gtk.CellRendererText() + cell1 = gtk.CellRendererText() + cell2 = gtk.CellRendererText() + cell3 = gtk.CellRendererText() + cell4 = gtk.CellRendererText() + cell5 = gtk.CellRendererText() + + typecol = gtk.TreeViewColumn(_("Type"), + cell0, text = 0) + typecol.set_sort_column_id(0) + typecol.set_resizable(True) + typecol.connect("clicked", + self.__on_treeviewcol_clicked, 0) + self.snaptreeview.append_column(typecol) + + mountptcol = gtk.TreeViewColumn(_("Mount Point"), + cell1, text = 1) + mountptcol.set_sort_column_id(1) + mountptcol.set_resizable(True) + mountptcol.connect("clicked", + self.__on_treeviewcol_clicked, 1) + self.snaptreeview.append_column(mountptcol) + + fsnamecol = gtk.TreeViewColumn(_("File System Name"), + cell2, text = 2) + fsnamecol.set_sort_column_id(2) + fsnamecol.set_resizable(True) + fsnamecol.connect("clicked", + self.__on_treeviewcol_clicked, 2) + self.snaptreeview.append_column(fsnamecol) + + snaplabelcol = gtk.TreeViewColumn(_("Snapshot Name"), + cell3, text = 3) + snaplabelcol.set_sort_column_id(3) + snaplabelcol.set_resizable(True) + snaplabelcol.connect("clicked", + self.__on_treeviewcol_clicked, 3) + self.snaptreeview.append_column(snaplabelcol) + + cell4.props.xalign = 1.0 + creationcol = gtk.TreeViewColumn(_("Creation Time"), + cell4, text = 4) + creationcol.set_sort_column_id(5) + creationcol.set_resizable(True) + creationcol.connect("clicked", + self.__on_treeviewcol_clicked, 5) + self.snaptreeview.append_column(creationcol) + + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + typestore = gtk.ListStore(str, str) + typestore.append([_("All"), "All"]) + typestore.append([_("Backups"), "Backup"]) + typestore.append([_("Snapshots"), "Snapshot"]) + + self.typefiltercombo = self.xml.get_widget("typefiltercombo") + self.typefiltercombo.set_model(typestore) + typefiltercomboCell = gtk.CellRendererText() + self.typefiltercombo.pack_start(typefiltercomboCell, True) + self.typefiltercombo.add_attribute(typefiltercomboCell, 'text',0) + + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + fsstore = gtk.ListStore(str, str) + fslist = self.datasets.list_filesystems() + fsstore.append([_("All"), None]) + for fsname,fsmount in fslist: + fsstore.append([fsname, fsname]) + self.fsfilterentry = self.xml.get_widget("fsfilterentry") + self.fsfilterentry.set_model(fsstore) + self.fsfilterentry.set_text_column(0) + fsfilterentryCell = gtk.CellRendererText() + self.fsfilterentry.pack_start(fsfilterentryCell) + + schedstore = gtk.ListStore(str, str) + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + schedstore.append([_("All"), None]) + schedstore.append([_("Monthly"), "monthly"]) + schedstore.append([_("Weekly"), "weekly"]) + schedstore.append([_("Daily"), "daily"]) + schedstore.append([_("Hourly"), "hourly"]) + schedstore.append([_("1/4 Hourly"), "frequent"]) + self.schedfilterentry = self.xml.get_widget("schedfilterentry") + self.schedfilterentry.set_model(schedstore) + self.schedfilterentry.set_text_column(0) + schedentryCell = gtk.CellRendererText() + self.schedfilterentry.pack_start(schedentryCell) + + self.schedfilterentry.set_active(0) + self.fsfilterentry.set_active(0) + self.typefiltercombo.set_active(0) + else: + cloned = self.datasets.list_cloned_snapshots() + num_snap = 0 + num_rsync = 0 + for snapname in self.shortcircuit: + # Filter out snapshots that are the root + # of cloned filesystems or volumes + try: + cloned.index(snapname) + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot can not be deleted")) + text = _("%s has one or more dependent clones " + "and will not be deleted. To delete " + "this snapshot, first delete all " + "datasets and snapshots cloned from " + "this snapshot.") \ + % snapname + dialog.format_secondary_text(text) + dialog.run() + sys.exit(1) + except ValueError: + path = os.path.abspath (snapname) + if not os.path.exists (path): + snapshot = zfs.Snapshot(snapname) + self.backuptodelete.append(snapshot) + num_snap += 1 + else: + self.backuptodelete.append(RsyncBackup (snapname)) + num_rsync += 1 + + confirm = self.xml.get_widget("confirmdialog") + summary = self.xml.get_widget("summarylabel") + total = len(self.backuptodelete) + + text = "" + if num_rsync != 0 : + if num_rsync == 1: + text = _("1 external backup will be deleted.") + else: + text = _("%d external backups will be deleted.") % num_rsync + + if num_snap != 0 : + if len(text) != 0: + text += "\n" + if num_snap == 1: + text += _("1 snapshot will be deleted.") + else: + text += _("%d snapshots will be deleted.") % num_snap + + summary.set_text(text ) + response = confirm.run() + if response != 2: + sys.exit(0) + else: + # Create the thread in an idle loop in order to + # avoid deadlock inside gtk. + glib.idle_add(self.__init_delete) + return False + + def __on_treeviewcol_clicked(self, widget, searchcol): + self.snaptreeview.set_search_column(searchcol) + + def __filter_snapshot_list(self, list, filesys = None, snap = None, btype = None): + if filesys == None and snap == None and btype == None: + return list + fssublist = [] + if filesys != None: + for snapshot in list: + if snapshot.fsname.find(filesys) != -1: + fssublist.append(snapshot) + else: + fssublist = list + + snaplist = [] + if snap != None: + for snapshot in fssublist: + if snapshot.snaplabel.find(snap) != -1: + snaplist.append(snapshot) + else: + snaplist = fssublist + + typelist = [] + if btype != None and btype != "All": + for item in snaplist: + if btype == "Backup": + if isinstance(item, RsyncBackup): + typelist.append (item) + else: + if isinstance(item, zfs.Snapshot): + typelist.append (item) + else: + typelist = snaplist + + return typelist + + def __on_filterentry_changed(self, widget): + # Get the filesystem filter value + iter = self.fsfilterentry.get_active_iter() + if iter == None: + filesys = self.fsfilterentry.get_active_text() + else: + model = self.fsfilterentry.get_model() + filesys = model.get(iter, 1)[0] + # Get the snapshot name filter value + iter = self.schedfilterentry.get_active_iter() + if iter == None: + snap = self.schedfilterentry.get_active_text() + else: + model = self.schedfilterentry.get_model() + snap = model.get(iter, 1)[0] + + # Get the type filter value + iter = self.typefiltercombo.get_active_iter() + if iter == None: + type = "All" + else: + model = self.typefiltercombo.get_model() + type = model.get(iter, 1)[0] + + self.liststorefs.clear() + newlist = self.__filter_snapshot_list(self.snapscanner.snapshots, + filesys, + snap, type) + for snapshot in newlist: + try: + tm = time.localtime(snapshot.get_creation_time()) + t = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + t = time.ctime(snapshot.get_creation_time()) + try: + mount_point = self.snapscanner.mounts[snapshot.fsname] + if (mount_point == "legacy"): + mount_point = _("Legacy") + + self.liststorefs.append([ + _("Snapshot"), + mount_point, + snapshot.fsname, + snapshot.snaplabel, + t, + snapshot.get_creation_time(), + snapshot]) + except KeyError: + continue + # This will catch exceptions from things we ignore + # such as dump as swap volumes and skip over them. + # add rsync backups + newlist = self.__filter_snapshot_list(self.snapscanner.rsynced_backups, + filesys, + snap, type) + for backup in newlist: + self.liststorefs.append([_("Backup"), + backup.zfs_mountpoint, + backup.fsname, + backup.snaplabel, + backup.creationtime_str, + backup.creationtime, + backup]) + + def __on_selectbutton_clicked(self, widget): + selection = self.snaptreeview.get_selection() + selection.select_all() + return + + def __on_deselectbutton_clicked(self, widget): + selection = self.snaptreeview.get_selection() + selection.unselect_all() + return + + def __on_deletebutton_clicked(self, widget): + self.backuptodelete = [] + selection = self.snaptreeview.get_selection() + selection.selected_foreach(self.__add_selection) + total = len(self.backuptodelete) + if total <= 0: + return + + confirm = self.xml.get_widget("confirmdialog") + summary = self.xml.get_widget("summarylabel") + + num_snap = 0 + num_rsync = 0 + for item in self.backuptodelete: + if isinstance (item, RsyncBackup): + num_rsync+=1 + else: + num_snap+=1 + + str = "" + if num_rsync != 0 : + if num_rsync == 1: + str = _("1 external backup will be deleted.") + else: + str = _("%d external backups will be deleted.") % num_rsync + + if num_snap != 0 : + if len(str) != 0: + str += "\n" + if num_snap == 1: + str += _("1 snapshot will be deleted.") + else: + str += _("%d snapshots will be deleted.") % num_snap + + summary.set_text(str) + response = confirm.run() + if response != 2: + return + else: + glib.idle_add(self.__init_delete) + return + + def __init_scan(self): + self.snapscanner = ScanSnapshots() + self.pulsedialog.show() + self.snapscanner.start() + glib.timeout_add(100, self.__monitor_scan) + return False + + def __init_delete(self): + self.snapdeleter = DeleteSnapshots(self.backuptodelete) + # If there's more than a few snapshots, pop up + # a progress bar. + if len(self.backuptodelete) > 3: + self.progressbar.set_fraction(0.0) + self.progressdialog.show() + self.snapdeleter.start() + glib.timeout_add(300, self.__monitor_deletion) + return False + + def __monitor_scan(self): + if self.snapscanner.isAlive() == True: + self.xml.get_widget("pulsebar").pulse() + return True + else: + self.pulsedialog.hide() + if self.snapscanner.errors: + details = "" + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Some snapshots could not be read")) + dialog.connect("response", + self.__on_errordialog_response) + for error in self.snapscanner.errors: + details = details + error + dialog.format_secondary_text(details) + dialog.show() + self.__on_filterentry_changed(None) + return False + + def __monitor_deletion(self): + if self.snapdeleter.isAlive() == True: + self.progressbar.set_fraction(self.snapdeleter.progress) + return True + else: + self.progressdialog.hide() + self.progressbar.set_fraction(1.0) + self.progressdialog.hide() + if self.snapdeleter.errors: + details = "" + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Some snapshots could not be deleted")) + dialog.connect("response", + self.__on_errordialog_response) + for error in self.snapdeleter.errors: + details = details + error + dialog.format_secondary_text(details) + dialog.show() + # If we didn't shortcircut straight to the delete confirmation + # dialog then the main dialog is visible so we rebuild the list + # view. + if len(self.shortcircuit) == 0: + self.__refresh_view() + else: + gtk.main_quit() + return False + + def __refresh_view(self): + self.liststorefs.clear() + glib.idle_add(self.__init_scan) + self.backuptodelete = [] + + def __add_selection(self, treemodel, path, iter): + snapshot = treemodel.get(iter, 6)[0] + self.backuptodelete.append(snapshot) + + def __on_confirmcancel_clicked(self, widget): + widget.get_toplevel().hide() + widget.get_toplevel().response(1) + + def __on_confirmdelete_clicked(self, widget): + widget.get_toplevel().hide() + widget.get_toplevel().response(2) + + def __on_errordialog_response(self, widget, responseid): + widget.hide() + +class ScanSnapshots(threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + self.errors = [] + self.datasets = zfs.Datasets() + self.snapshots = [] + self.rsynced_fs = [] + self.rsynced_backups = [] + + def run(self): + self.mounts = self.__get_fs_mountpoints() + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + self.__get_rsync_backups () + self.rescan() + + def __get_rsync_backups (self): + # get rsync backup dir + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsyncDir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + if not os.path.exists(self.rsyncDir): + return + + rootBackupDirs = [] + + for root, dirs, files in os.walk(self.rsyncDir): + if '.time-slider' in dirs: + dirs.remove('.time-slider') + backupDir = os.path.join(root, rsyncsmf.RSYNCDIRSUFFIX) + if os.path.exists(backupDir): + insort(rootBackupDirs, os.path.abspath(backupDir)) + + for dirName in rootBackupDirs: + os.chdir(dirName) + for d in os.listdir(dirName): + if os.path.isdir(d) and not os.path.islink(d): + s1 = dirName.split ("%s/" % self.rsyncDir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + fs = s2[0] + + rb = RsyncBackup ("%s/%s" %(dirName, d), + self.rsyncDir, + fs, + d, + os.stat(d).st_mtime) + self.rsynced_backups.append (rb) + + def __get_fs_mountpoints(self): + """Returns a dictionary mapping: + {filesystem : mountpoint}""" + result = {} + for filesys,mountpoint in self.datasets.list_filesystems(): + result[filesys] = mountpoint + return result + + def rescan(self): + cloned = self.datasets.list_cloned_snapshots() + self.snapshots = [] + snaplist = self.datasets.list_snapshots() + for snapname,snaptime in snaplist: + # Filter out snapshots that are the root + # of cloned filesystems or volumes + try: + cloned.index(snapname) + except ValueError: + snapshot = zfs.Snapshot(snapname, snaptime) + self.snapshots.append(snapshot) + +class DeleteSnapshots(threading.Thread): + + def __init__(self, snapshots): + threading.Thread.__init__(self) + self.backuptodelete = snapshots + self.started = False + self.completed = False + self.progress = 0.0 + self.errors = [] + + def run(self): + deleted = 0 + self.started = True + total = len(self.backuptodelete) + for backup in self.backuptodelete: + # The backup could have expired and been automatically + # destroyed since the user selected it. Check that it + # still exists before attempting to delete it. If it + # doesn't exist just silently ignore it. + if backup.exists(): + try: + backup.destroy () + except RuntimeError, inst: + self.errors.append(str(inst)) + deleted += 1 + self.progress = deleted / (total * 1.0) + self.completed = True + +def main(argv): + try: + opts,args = getopt.getopt(sys.argv[1:], "", []) + except getopt.GetoptError: + sys.exit(2) + rbacp = RBACprofile() + if os.geteuid() == 0: + if len(args) > 0: + manager = DeleteSnapManager(args) + else: + manager = DeleteSnapManager() + gtk.gdk.threads_enter() + glib.idle_add(manager.initialise_view) + gtk.main() + gtk.gdk.threads_leave() + elif os.path.exists(argv) and os.path.exists("/usr/bin/gksu"): + # Run via gksu, which will prompt for the root password + newargs = ["gksu", argv] + for arg in args: + newargs.append(arg) + os.execv("/usr/bin/gksu", newargs); + # Shouldn't reach this point + sys.exit(1) + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Insufficient Priviliges")) + dialog.format_secondary_text(_("Snapshot deletion requires " + "administrative privileges to run. " + "You have not been assigned the necessary" + "administrative priviliges." + "\n\nConsult your system administrator ")) + dialog.run() + print argv + "is not a valid executable path" + sys.exit(1) diff --git a/usr/share/time-slider/lib/time_slider/deletegui.py.bak b/usr/share/time-slider/lib/time_slider/deletegui.py.bak new file mode 100644 index 0000000..dd157f5 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/deletegui.py.bak @@ -0,0 +1,766 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import threading +import sys +import os +import time +import getopt +import locale +import shutil +import fcntl +from bisect import insort + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import glib + import gobject +except: + sys.exit(1) + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import rsyncsmf + + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from rbac import RBACprofile + +class RsyncBackup: + + def __init__(self, mountpoint, rsync_dir = None, fsname= None, snaplabel= None, creationtime= None): + + if rsync_dir == None: + self.__init_from_mp (mountpoint) + else: + self.rsync_dir = rsync_dir + self.mountpoint = mountpoint + self.fsname = fsname + self.snaplabel = snaplabel + + self.creationtime = creationtime + try: + tm = time.localtime(self.creationtime) + self.creationtime_str = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + self.creationtime_str = time.ctime(self.creationtime) + fs = zfs.Filesystem (self.fsname) + self.zfs_mountpoint = fs.get_mountpoint () + + def __init_from_mp (self, mountpoint): + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsync_dir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + self.mountpoint = mountpoint + + s1 = mountpoint.split ("%s/" % self.rsync_dir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + s3 = s2[1].split ('/',2) + self.fsname = s2[0] + self.snaplabel = s3[1] + self.creationtime = os.stat(mountpoint).st_mtime + + def __str__(self): + ret = "self.rsync_dir = %s\n \ + self.mountpoint = %s\n \ + self.fsname = %s\n \ + self.snaplabel = %s\n" % (self.rsync_dir, + self.mountpoint, self.fsname, + self.snaplabel) + return ret + + + def exists(self): + return os.path.exists(self.mountpoint) + + def destroy(self): + lockFileDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOCKSUFFIX) + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + lockFile = os.path.join(lockFileDir, self.snaplabel + ".lock") + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + raise RuntimeError, \ + "couldn't delete %s, already used by another process" % self.mountpoint + return + + trashDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX) + if not os.path.exists(trashDir): + os.makedirs(trashDir, 0755) + + backupTrashDir = os.path.join (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX, + self.snaplabel) + + # move then delete + os.rename (self.mountpoint, backupTrashDir) + shutil.rmtree (backupTrashDir) + + log = "%s/%s/%s/%s.log" % (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOGSUFFIX, + self.snaplabel) + if os.path.exists (log): + os.unlink (log) + + lockFp.close() + os.unlink(lockFile) + +class DeleteSnapManager: + + def __init__(self, snapshots = None): + self.xml = gtk.glade.XML("%s/../../glade/time-slider-delete.glade" \ + % (os.path.dirname(__file__))) + self.backuptodelete = [] + self.shortcircuit = [] + maindialog = self.xml.get_widget("time-slider-delete") + self.pulsedialog = self.xml.get_widget("pulsedialog") + self.pulsedialog.set_transient_for(maindialog) + self.datasets = zfs.Datasets() + if snapshots: + maindialog.hide() + self.shortcircuit = snapshots + else: + glib.idle_add(self.__init_scan) + + self.progressdialog = self.xml.get_widget("deletingdialog") + self.progressdialog.set_transient_for(maindialog) + self.progressbar = self.xml.get_widget("deletingprogress") + # signal dictionary + dic = {"on_closebutton_clicked" : gtk.main_quit, + "on_window_delete_event" : gtk.main_quit, + "on_snapshotmanager_delete_event" : gtk.main_quit, + "on_fsfilterentry_changed" : self.__on_filterentry_changed, + "on_schedfilterentry_changed" : self.__on_filterentry_changed, + "on_typefiltercombo_changed" : self.__on_filterentry_changed, + "on_selectbutton_clicked" : self.__on_selectbutton_clicked, + "on_deselectbutton_clicked" : self.__on_deselectbutton_clicked, + "on_deletebutton_clicked" : self.__on_deletebutton_clicked, + "on_confirmcancel_clicked" : self.__on_confirmcancel_clicked, + "on_confirmdelete_clicked" : self.__on_confirmdelete_clicked, + "on_errordialog_response" : self.__on_errordialog_response} + self.xml.signal_autoconnect(dic) + + def initialise_view(self): + if len(self.shortcircuit) == 0: + # Set TreeViews + self.liststorefs = gtk.ListStore(str, str, str, str, str, long, + gobject.TYPE_PYOBJECT) + list_filter = self.liststorefs.filter_new() + list_sort = gtk.TreeModelSort(list_filter) + list_sort.set_sort_column_id(1, gtk.SORT_ASCENDING) + + self.snaptreeview = self.xml.get_widget("snaplist") + self.snaptreeview.set_model(self.liststorefs) + self.snaptreeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + + cell0 = gtk.CellRendererText() + cell1 = gtk.CellRendererText() + cell2 = gtk.CellRendererText() + cell3 = gtk.CellRendererText() + cell4 = gtk.CellRendererText() + cell5 = gtk.CellRendererText() + + typecol = gtk.TreeViewColumn(_("Type"), + cell0, text = 0) + typecol.set_sort_column_id(0) + typecol.set_resizable(True) + typecol.connect("clicked", + self.__on_treeviewcol_clicked, 0) + self.snaptreeview.append_column(typecol) + + mountptcol = gtk.TreeViewColumn(_("Mount Point"), + cell1, text = 1) + mountptcol.set_sort_column_id(1) + mountptcol.set_resizable(True) + mountptcol.connect("clicked", + self.__on_treeviewcol_clicked, 1) + self.snaptreeview.append_column(mountptcol) + + fsnamecol = gtk.TreeViewColumn(_("File System Name"), + cell2, text = 2) + fsnamecol.set_sort_column_id(2) + fsnamecol.set_resizable(True) + fsnamecol.connect("clicked", + self.__on_treeviewcol_clicked, 2) + self.snaptreeview.append_column(fsnamecol) + + snaplabelcol = gtk.TreeViewColumn(_("Snapshot Name"), + cell3, text = 3) + snaplabelcol.set_sort_column_id(3) + snaplabelcol.set_resizable(True) + snaplabelcol.connect("clicked", + self.__on_treeviewcol_clicked, 3) + self.snaptreeview.append_column(snaplabelcol) + + cell4.props.xalign = 1.0 + creationcol = gtk.TreeViewColumn(_("Creation Time"), + cell4, text = 4) + creationcol.set_sort_column_id(5) + creationcol.set_resizable(True) + creationcol.connect("clicked", + self.__on_treeviewcol_clicked, 5) + self.snaptreeview.append_column(creationcol) + + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + typestore = gtk.ListStore(str, str) + typestore.append([_("All"), "All"]) + typestore.append([_("Backups"), "Backup"]) + typestore.append([_("Snapshots"), "Snapshot"]) + + self.typefiltercombo = self.xml.get_widget("typefiltercombo") + self.typefiltercombo.set_model(typestore) + typefiltercomboCell = gtk.CellRendererText() + self.typefiltercombo.pack_start(typefiltercomboCell, True) + self.typefiltercombo.add_attribute(typefiltercomboCell, 'text',0) + + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + fsstore = gtk.ListStore(str, str) + fslist = self.datasets.list_filesystems() + fsstore.append([_("All"), None]) + for fsname,fsmount in fslist: + fsstore.append([fsname, fsname]) + self.fsfilterentry = self.xml.get_widget("fsfilterentry") + self.fsfilterentry.set_model(fsstore) + self.fsfilterentry.set_text_column(0) + fsfilterentryCell = gtk.CellRendererText() + self.fsfilterentry.pack_start(fsfilterentryCell) + + schedstore = gtk.ListStore(str, str) + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + schedstore.append([_("All"), None]) + schedstore.append([_("Monthly"), "monthly"]) + schedstore.append([_("Weekly"), "weekly"]) + schedstore.append([_("Daily"), "daily"]) + schedstore.append([_("Hourly"), "hourly"]) + schedstore.append([_("1/4 Hourly"), "frequent"]) + self.schedfilterentry = self.xml.get_widget("schedfilterentry") + self.schedfilterentry.set_model(schedstore) + self.schedfilterentry.set_text_column(0) + schedentryCell = gtk.CellRendererText() + self.schedfilterentry.pack_start(schedentryCell) + + self.schedfilterentry.set_active(0) + self.fsfilterentry.set_active(0) + self.typefiltercombo.set_active(0) + else: + cloned = self.datasets.list_cloned_snapshots() + num_snap = 0 + num_rsync = 0 + for snapname in self.shortcircuit: + # Filter out snapshots that are the root + # of cloned filesystems or volumes + try: + cloned.index(snapname) + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot can not be deleted")) + text = _("%s has one or more dependent clones " + "and will not be deleted. To delete " + "this snapshot, first delete all " + "datasets and snapshots cloned from " + "this snapshot.") \ + % snapname + dialog.format_secondary_text(text) + dialog.run() + sys.exit(1) + except ValueError: + path = os.path.abspath (snapname) + if not os.path.exists (path): + snapshot = zfs.Snapshot(snapname) + self.backuptodelete.append(snapshot) + num_snap += 1 + else: + self.backuptodelete.append(RsyncBackup (snapname)) + num_rsync += 1 + + confirm = self.xml.get_widget("confirmdialog") + summary = self.xml.get_widget("summarylabel") + total = len(self.backuptodelete) + + text = "" + if num_rsync != 0 : + if num_rsync == 1: + text = _("1 external backup will be deleted.") + else: + text = _("%d external backups will be deleted.") % num_rsync + + if num_snap != 0 : + if len(text) != 0: + text += "\n" + if num_snap == 1: + text += _("1 snapshot will be deleted.") + else: + text += _("%d snapshots will be deleted.") % num_snap + + summary.set_text(text ) + response = confirm.run() + if response != 2: + sys.exit(0) + else: + # Create the thread in an idle loop in order to + # avoid deadlock inside gtk. + glib.idle_add(self.__init_delete) + return False + + def __on_treeviewcol_clicked(self, widget, searchcol): + self.snaptreeview.set_search_column(searchcol) + + def __filter_snapshot_list(self, list, filesys = None, snap = None, btype = None): + if filesys == None and snap == None and btype == None: + return list + fssublist = [] + if filesys != None: + for snapshot in list: + if snapshot.fsname.find(filesys) != -1: + fssublist.append(snapshot) + else: + fssublist = list + + snaplist = [] + if snap != None: + for snapshot in fssublist: + if snapshot.snaplabel.find(snap) != -1: + snaplist.append(snapshot) + else: + snaplist = fssublist + + typelist = [] + if btype != None and btype != "All": + for item in snaplist: + if btype == "Backup": + if isinstance(item, RsyncBackup): + typelist.append (item) + else: + if isinstance(item, zfs.Snapshot): + typelist.append (item) + else: + typelist = snaplist + + return typelist + + def __on_filterentry_changed(self, widget): + # Get the filesystem filter value + iter = self.fsfilterentry.get_active_iter() + if iter == None: + filesys = self.fsfilterentry.get_active_text() + else: + model = self.fsfilterentry.get_model() + filesys = model.get(iter, 1)[0] + # Get the snapshot name filter value + iter = self.schedfilterentry.get_active_iter() + if iter == None: + snap = self.schedfilterentry.get_active_text() + else: + model = self.schedfilterentry.get_model() + snap = model.get(iter, 1)[0] + + # Get the type filter value + iter = self.typefiltercombo.get_active_iter() + if iter == None: + type = "All" + else: + model = self.typefiltercombo.get_model() + type = model.get(iter, 1)[0] + + self.liststorefs.clear() + newlist = self.__filter_snapshot_list(self.snapscanner.snapshots, + filesys, + snap, type) + for snapshot in newlist: + try: + tm = time.localtime(snapshot.get_creation_time()) + t = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + t = time.ctime(snapshot.get_creation_time()) + try: + mount_point = self.snapscanner.mounts[snapshot.fsname] + if (mount_point == "legacy"): + mount_point = _("Legacy") + + self.liststorefs.append([ + _("Snapshot"), + mount_point, + snapshot.fsname, + snapshot.snaplabel, + t, + snapshot.get_creation_time(), + snapshot]) + except KeyError: + continue + # This will catch exceptions from things we ignore + # such as dump as swap volumes and skip over them. + # add rsync backups + newlist = self.__filter_snapshot_list(self.snapscanner.rsynced_backups, + filesys, + snap, type) + for backup in newlist: + self.liststorefs.append([_("Backup"), + backup.zfs_mountpoint, + backup.fsname, + backup.snaplabel, + backup.creationtime_str, + backup.creationtime, + backup]) + + def __on_selectbutton_clicked(self, widget): + selection = self.snaptreeview.get_selection() + selection.select_all() + return + + def __on_deselectbutton_clicked(self, widget): + selection = self.snaptreeview.get_selection() + selection.unselect_all() + return + + def __on_deletebutton_clicked(self, widget): + self.backuptodelete = [] + selection = self.snaptreeview.get_selection() + selection.selected_foreach(self.__add_selection) + total = len(self.backuptodelete) + if total <= 0: + return + + confirm = self.xml.get_widget("confirmdialog") + summary = self.xml.get_widget("summarylabel") + + num_snap = 0 + num_rsync = 0 + for item in self.backuptodelete: + if isinstance (item, RsyncBackup): + num_rsync+=1 + else: + num_snap+=1 + + str = "" + if num_rsync != 0 : + if num_rsync == 1: + str = _("1 external backup will be deleted.") + else: + str = _("%d external backups will be deleted.") % num_rsync + + if num_snap != 0 : + if len(str) != 0: + str += "\n" + if num_snap == 1: + str += _("1 snapshot will be deleted.") + else: + str += _("%d snapshots will be deleted.") % num_snap + + summary.set_text(str) + response = confirm.run() + if response != 2: + return + else: + glib.idle_add(self.__init_delete) + return + + def __init_scan(self): + self.snapscanner = ScanSnapshots() + self.pulsedialog.show() + self.snapscanner.start() + glib.timeout_add(100, self.__monitor_scan) + return False + + def __init_delete(self): + self.snapdeleter = DeleteSnapshots(self.backuptodelete) + # If there's more than a few snapshots, pop up + # a progress bar. + if len(self.backuptodelete) > 3: + self.progressbar.set_fraction(0.0) + self.progressdialog.show() + self.snapdeleter.start() + glib.timeout_add(300, self.__monitor_deletion) + return False + + def __monitor_scan(self): + if self.snapscanner.isAlive() == True: + self.xml.get_widget("pulsebar").pulse() + return True + else: + self.pulsedialog.hide() + if self.snapscanner.errors: + details = "" + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Some snapshots could not be read")) + dialog.connect("response", + self.__on_errordialog_response) + for error in self.snapscanner.errors: + details = details + error + dialog.format_secondary_text(details) + dialog.show() + self.__on_filterentry_changed(None) + return False + + def __monitor_deletion(self): + if self.snapdeleter.isAlive() == True: + self.progressbar.set_fraction(self.snapdeleter.progress) + return True + else: + self.progressdialog.hide() + self.progressbar.set_fraction(1.0) + self.progressdialog.hide() + if self.snapdeleter.errors: + details = "" + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Some snapshots could not be deleted")) + dialog.connect("response", + self.__on_errordialog_response) + for error in self.snapdeleter.errors: + details = details + error + dialog.format_secondary_text(details) + dialog.show() + # If we didn't shortcircut straight to the delete confirmation + # dialog then the main dialog is visible so we rebuild the list + # view. + if len(self.shortcircuit) == 0: + self.__refresh_view() + else: + gtk.main_quit() + return False + + def __refresh_view(self): + self.liststorefs.clear() + glib.idle_add(self.__init_scan) + self.backuptodelete = [] + + def __add_selection(self, treemodel, path, iter): + snapshot = treemodel.get(iter, 8)[0] + self.backuptodelete.append(snapshot) + + def __on_confirmcancel_clicked(self, widget): + widget.get_toplevel().hide() + widget.get_toplevel().response(1) + + def __on_confirmdelete_clicked(self, widget): + widget.get_toplevel().hide() + widget.get_toplevel().response(2) + + def __on_errordialog_response(self, widget, responseid): + widget.hide() + +class ScanSnapshots(threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + self.errors = [] + self.datasets = zfs.Datasets() + self.snapshots = [] + self.rsynced_fs = [] + self.rsynced_backups = [] + + def run(self): + self.mounts = self.__get_fs_mountpoints() + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + self.__get_rsync_backups () + self.rescan() + + def __get_rsync_backups (self): + # get rsync backup dir + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsyncDir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + if not os.path.exists(self.rsyncDir): + return + + rootBackupDirs = [] + + for root, dirs, files in os.walk(self.rsyncDir): + if '.time-slider' in dirs: + dirs.remove('.time-slider') + backupDir = os.path.join(root, rsyncsmf.RSYNCDIRSUFFIX) + if os.path.exists(backupDir): + insort(rootBackupDirs, os.path.abspath(backupDir)) + + for dirName in rootBackupDirs: + os.chdir(dirName) + for d in os.listdir(dirName): + if os.path.isdir(d) and not os.path.islink(d): + s1 = dirName.split ("%s/" % self.rsyncDir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + fs = s2[0] + + rb = RsyncBackup ("%s/%s" %(dirName, d), + self.rsyncDir, + fs, + d, + os.stat(d).st_mtime) + self.rsynced_backups.append (rb) + + def __get_fs_mountpoints(self): + """Returns a dictionary mapping: + {filesystem : mountpoint}""" + result = {} + for filesys,mountpoint in self.datasets.list_filesystems(): + result[filesys] = mountpoint + return result + + def rescan(self): + cloned = self.datasets.list_cloned_snapshots() + self.snapshots = [] + snaplist = self.datasets.list_snapshots() + for snapname,snaptime in snaplist: + # Filter out snapshots that are the root + # of cloned filesystems or volumes + try: + cloned.index(snapname) + except ValueError: + snapshot = zfs.Snapshot(snapname, snaptime) + self.snapshots.append(snapshot) + +class DeleteSnapshots(threading.Thread): + + def __init__(self, snapshots): + threading.Thread.__init__(self) + self.backuptodelete = snapshots + self.started = False + self.completed = False + self.progress = 0.0 + self.errors = [] + + def run(self): + deleted = 0 + self.started = True + total = len(self.backuptodelete) + for backup in self.backuptodelete: + # The backup could have expired and been automatically + # destroyed since the user selected it. Check that it + # still exists before attempting to delete it. If it + # doesn't exist just silently ignore it. + if backup.exists(): + try: + backup.destroy () + except RuntimeError, inst: + self.errors.append(str(inst)) + deleted += 1 + self.progress = deleted / (total * 1.0) + self.completed = True + +def main(argv): + try: + opts,args = getopt.getopt(sys.argv[1:], "", []) + except getopt.GetoptError: + sys.exit(2) + rbacp = RBACprofile() + if os.geteuid() == 0: + if len(args) > 0: + manager = DeleteSnapManager(args) + else: + manager = DeleteSnapManager() + gtk.gdk.threads_enter() + glib.idle_add(manager.initialise_view) + gtk.main() + gtk.gdk.threads_leave() + elif rbacp.has_profile("Primary Administrator") or \ + rbacp.has_profile("ZFS File System Management"): + # Run via pfexec, which will launch the GUI as superuser + arguments = [] + arguments.append ("pfexec") + arguments.append (argv) + arguments += args + os.execv("/usr/bin/pfexec", arguments) + # Shouldn't reach this point + sys.exit(1) + elif os.path.exists(argv) and os.path.exists("/usr/bin/gksu"): + # Run via gksu, which will prompt for the root password + newargs = ["gksu", argv] + for arg in args: + newargs.append(arg) + os.execv("/usr/bin/gksu", newargs); + # Shouldn't reach this point + sys.exit(1) + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Insufficient Priviliges")) + dialog.format_secondary_text(_("Snapshot deletion requires " + "administrative privileges to run. " + "You have not been assigned the necessary" + "administrative priviliges." + "\n\nConsult your system administrator ")) + dialog.run() + print argv + "is not a valid executable path" + sys.exit(1) diff --git a/usr/share/time-slider/lib/time_slider/deletegui.py~ b/usr/share/time-slider/lib/time_slider/deletegui.py~ new file mode 100755 index 0000000..8023d9a --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/deletegui.py~ @@ -0,0 +1,756 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import threading +import sys +import os +import time +import getopt +import locale +import shutil +import fcntl +from bisect import insort + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import glib + import gobject +except: + sys.exit(1) + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import rsyncsmf + + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from rbac import RBACprofile + +class RsyncBackup: + + def __init__(self, mountpoint, rsync_dir = None, fsname= None, snaplabel= None, creationtime= None): + + if rsync_dir == None: + self.__init_from_mp (mountpoint) + else: + self.rsync_dir = rsync_dir + self.mountpoint = mountpoint + self.fsname = fsname + self.snaplabel = snaplabel + + self.creationtime = creationtime + try: + tm = time.localtime(self.creationtime) + self.creationtime_str = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + self.creationtime_str = time.ctime(self.creationtime) + fs = zfs.Filesystem (self.fsname) + self.zfs_mountpoint = fs.get_mountpoint () + + def __init_from_mp (self, mountpoint): + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsync_dir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + self.mountpoint = mountpoint + + s1 = mountpoint.split ("%s/" % self.rsync_dir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + s3 = s2[1].split ('/',2) + self.fsname = s2[0] + self.snaplabel = s3[1] + self.creationtime = os.stat(mountpoint).st_mtime + + def __str__(self): + ret = "self.rsync_dir = %s\n \ + self.mountpoint = %s\n \ + self.fsname = %s\n \ + self.snaplabel = %s\n" % (self.rsync_dir, + self.mountpoint, self.fsname, + self.snaplabel) + return ret + + + def exists(self): + return os.path.exists(self.mountpoint) + + def destroy(self): + lockFileDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOCKSUFFIX) + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + lockFile = os.path.join(lockFileDir, self.snaplabel + ".lock") + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + raise RuntimeError, \ + "couldn't delete %s, already used by another process" % self.mountpoint + return + + trashDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX) + if not os.path.exists(trashDir): + os.makedirs(trashDir, 0755) + + backupTrashDir = os.path.join (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX, + self.snaplabel) + + # move then delete + os.rename (self.mountpoint, backupTrashDir) + shutil.rmtree (backupTrashDir) + + log = "%s/%s/%s/%s.log" % (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOGSUFFIX, + self.snaplabel) + if os.path.exists (log): + os.unlink (log) + + lockFp.close() + os.unlink(lockFile) + +class DeleteSnapManager: + + def __init__(self, snapshots = None): + self.xml = gtk.glade.XML("%s/../../glade/time-slider-delete.glade" \ + % (os.path.dirname(__file__))) + self.backuptodelete = [] + self.shortcircuit = [] + maindialog = self.xml.get_widget("time-slider-delete") + self.pulsedialog = self.xml.get_widget("pulsedialog") + self.pulsedialog.set_transient_for(maindialog) + self.datasets = zfs.Datasets() + if snapshots: + maindialog.hide() + self.shortcircuit = snapshots + else: + glib.idle_add(self.__init_scan) + + self.progressdialog = self.xml.get_widget("deletingdialog") + self.progressdialog.set_transient_for(maindialog) + self.progressbar = self.xml.get_widget("deletingprogress") + # signal dictionary + dic = {"on_closebutton_clicked" : gtk.main_quit, + "on_window_delete_event" : gtk.main_quit, + "on_snapshotmanager_delete_event" : gtk.main_quit, + "on_fsfilterentry_changed" : self.__on_filterentry_changed, + "on_schedfilterentry_changed" : self.__on_filterentry_changed, + "on_typefiltercombo_changed" : self.__on_filterentry_changed, + "on_selectbutton_clicked" : self.__on_selectbutton_clicked, + "on_deselectbutton_clicked" : self.__on_deselectbutton_clicked, + "on_deletebutton_clicked" : self.__on_deletebutton_clicked, + "on_confirmcancel_clicked" : self.__on_confirmcancel_clicked, + "on_confirmdelete_clicked" : self.__on_confirmdelete_clicked, + "on_errordialog_response" : self.__on_errordialog_response} + self.xml.signal_autoconnect(dic) + + def initialise_view(self): + if len(self.shortcircuit) == 0: + # Set TreeViews + self.liststorefs = gtk.ListStore(str, str, str, str, str, long, + gobject.TYPE_PYOBJECT) + list_filter = self.liststorefs.filter_new() + list_sort = gtk.TreeModelSort(list_filter) + list_sort.set_sort_column_id(1, gtk.SORT_ASCENDING) + + self.snaptreeview = self.xml.get_widget("snaplist") + self.snaptreeview.set_model(self.liststorefs) + self.snaptreeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + + cell0 = gtk.CellRendererText() + cell1 = gtk.CellRendererText() + cell2 = gtk.CellRendererText() + cell3 = gtk.CellRendererText() + cell4 = gtk.CellRendererText() + cell5 = gtk.CellRendererText() + + typecol = gtk.TreeViewColumn(_("Type"), + cell0, text = 0) + typecol.set_sort_column_id(0) + typecol.set_resizable(True) + typecol.connect("clicked", + self.__on_treeviewcol_clicked, 0) + self.snaptreeview.append_column(typecol) + + mountptcol = gtk.TreeViewColumn(_("Mount Point"), + cell1, text = 1) + mountptcol.set_sort_column_id(1) + mountptcol.set_resizable(True) + mountptcol.connect("clicked", + self.__on_treeviewcol_clicked, 1) + self.snaptreeview.append_column(mountptcol) + + fsnamecol = gtk.TreeViewColumn(_("File System Name"), + cell2, text = 2) + fsnamecol.set_sort_column_id(2) + fsnamecol.set_resizable(True) + fsnamecol.connect("clicked", + self.__on_treeviewcol_clicked, 2) + self.snaptreeview.append_column(fsnamecol) + + snaplabelcol = gtk.TreeViewColumn(_("Snapshot Name"), + cell3, text = 3) + snaplabelcol.set_sort_column_id(3) + snaplabelcol.set_resizable(True) + snaplabelcol.connect("clicked", + self.__on_treeviewcol_clicked, 3) + self.snaptreeview.append_column(snaplabelcol) + + cell4.props.xalign = 1.0 + creationcol = gtk.TreeViewColumn(_("Creation Time"), + cell4, text = 4) + creationcol.set_sort_column_id(5) + creationcol.set_resizable(True) + creationcol.connect("clicked", + self.__on_treeviewcol_clicked, 5) + self.snaptreeview.append_column(creationcol) + + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + typestore = gtk.ListStore(str, str) + typestore.append([_("All"), "All"]) + typestore.append([_("Backups"), "Backup"]) + typestore.append([_("Snapshots"), "Snapshot"]) + + self.typefiltercombo = self.xml.get_widget("typefiltercombo") + self.typefiltercombo.set_model(typestore) + typefiltercomboCell = gtk.CellRendererText() + self.typefiltercombo.pack_start(typefiltercomboCell, True) + self.typefiltercombo.add_attribute(typefiltercomboCell, 'text',0) + + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + fsstore = gtk.ListStore(str, str) + fslist = self.datasets.list_filesystems() + fsstore.append([_("All"), None]) + for fsname,fsmount in fslist: + fsstore.append([fsname, fsname]) + self.fsfilterentry = self.xml.get_widget("fsfilterentry") + self.fsfilterentry.set_model(fsstore) + self.fsfilterentry.set_text_column(0) + fsfilterentryCell = gtk.CellRendererText() + self.fsfilterentry.pack_start(fsfilterentryCell) + + schedstore = gtk.ListStore(str, str) + # Note to developers. + # The second element is for internal matching and should not + # be i18ned under any circumstances. + schedstore.append([_("All"), None]) + schedstore.append([_("Monthly"), "monthly"]) + schedstore.append([_("Weekly"), "weekly"]) + schedstore.append([_("Daily"), "daily"]) + schedstore.append([_("Hourly"), "hourly"]) + schedstore.append([_("1/4 Hourly"), "frequent"]) + self.schedfilterentry = self.xml.get_widget("schedfilterentry") + self.schedfilterentry.set_model(schedstore) + self.schedfilterentry.set_text_column(0) + schedentryCell = gtk.CellRendererText() + self.schedfilterentry.pack_start(schedentryCell) + + self.schedfilterentry.set_active(0) + self.fsfilterentry.set_active(0) + self.typefiltercombo.set_active(0) + else: + cloned = self.datasets.list_cloned_snapshots() + num_snap = 0 + num_rsync = 0 + for snapname in self.shortcircuit: + # Filter out snapshots that are the root + # of cloned filesystems or volumes + try: + cloned.index(snapname) + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot can not be deleted")) + text = _("%s has one or more dependent clones " + "and will not be deleted. To delete " + "this snapshot, first delete all " + "datasets and snapshots cloned from " + "this snapshot.") \ + % snapname + dialog.format_secondary_text(text) + dialog.run() + sys.exit(1) + except ValueError: + path = os.path.abspath (snapname) + if not os.path.exists (path): + snapshot = zfs.Snapshot(snapname) + self.backuptodelete.append(snapshot) + num_snap += 1 + else: + self.backuptodelete.append(RsyncBackup (snapname)) + num_rsync += 1 + + confirm = self.xml.get_widget("confirmdialog") + summary = self.xml.get_widget("summarylabel") + total = len(self.backuptodelete) + + text = "" + if num_rsync != 0 : + if num_rsync == 1: + text = _("1 external backup will be deleted.") + else: + text = _("%d external backups will be deleted.") % num_rsync + + if num_snap != 0 : + if len(text) != 0: + text += "\n" + if num_snap == 1: + text += _("1 snapshot will be deleted.") + else: + text += _("%d snapshots will be deleted.") % num_snap + + summary.set_text(text ) + response = confirm.run() + if response != 2: + sys.exit(0) + else: + # Create the thread in an idle loop in order to + # avoid deadlock inside gtk. + glib.idle_add(self.__init_delete) + return False + + def __on_treeviewcol_clicked(self, widget, searchcol): + self.snaptreeview.set_search_column(searchcol) + + def __filter_snapshot_list(self, list, filesys = None, snap = None, btype = None): + if filesys == None and snap == None and btype == None: + return list + fssublist = [] + if filesys != None: + for snapshot in list: + if snapshot.fsname.find(filesys) != -1: + fssublist.append(snapshot) + else: + fssublist = list + + snaplist = [] + if snap != None: + for snapshot in fssublist: + if snapshot.snaplabel.find(snap) != -1: + snaplist.append(snapshot) + else: + snaplist = fssublist + + typelist = [] + if btype != None and btype != "All": + for item in snaplist: + if btype == "Backup": + if isinstance(item, RsyncBackup): + typelist.append (item) + else: + if isinstance(item, zfs.Snapshot): + typelist.append (item) + else: + typelist = snaplist + + return typelist + + def __on_filterentry_changed(self, widget): + # Get the filesystem filter value + iter = self.fsfilterentry.get_active_iter() + if iter == None: + filesys = self.fsfilterentry.get_active_text() + else: + model = self.fsfilterentry.get_model() + filesys = model.get(iter, 1)[0] + # Get the snapshot name filter value + iter = self.schedfilterentry.get_active_iter() + if iter == None: + snap = self.schedfilterentry.get_active_text() + else: + model = self.schedfilterentry.get_model() + snap = model.get(iter, 1)[0] + + # Get the type filter value + iter = self.typefiltercombo.get_active_iter() + if iter == None: + type = "All" + else: + model = self.typefiltercombo.get_model() + type = model.get(iter, 1)[0] + + self.liststorefs.clear() + newlist = self.__filter_snapshot_list(self.snapscanner.snapshots, + filesys, + snap, type) + for snapshot in newlist: + try: + tm = time.localtime(snapshot.get_creation_time()) + t = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + t = time.ctime(snapshot.get_creation_time()) + try: + mount_point = self.snapscanner.mounts[snapshot.fsname] + if (mount_point == "legacy"): + mount_point = _("Legacy") + + self.liststorefs.append([ + _("Snapshot"), + mount_point, + snapshot.fsname, + snapshot.snaplabel, + t, + snapshot.get_creation_time(), + snapshot]) + except KeyError: + continue + # This will catch exceptions from things we ignore + # such as dump as swap volumes and skip over them. + # add rsync backups + newlist = self.__filter_snapshot_list(self.snapscanner.rsynced_backups, + filesys, + snap, type) + for backup in newlist: + self.liststorefs.append([_("Backup"), + backup.zfs_mountpoint, + backup.fsname, + backup.snaplabel, + backup.creationtime_str, + backup.creationtime, + backup]) + + def __on_selectbutton_clicked(self, widget): + selection = self.snaptreeview.get_selection() + selection.select_all() + return + + def __on_deselectbutton_clicked(self, widget): + selection = self.snaptreeview.get_selection() + selection.unselect_all() + return + + def __on_deletebutton_clicked(self, widget): + self.backuptodelete = [] + selection = self.snaptreeview.get_selection() + selection.selected_foreach(self.__add_selection) + total = len(self.backuptodelete) + if total <= 0: + return + + confirm = self.xml.get_widget("confirmdialog") + summary = self.xml.get_widget("summarylabel") + + num_snap = 0 + num_rsync = 0 + for item in self.backuptodelete: + if isinstance (item, RsyncBackup): + num_rsync+=1 + else: + num_snap+=1 + + str = "" + if num_rsync != 0 : + if num_rsync == 1: + str = _("1 external backup will be deleted.") + else: + str = _("%d external backups will be deleted.") % num_rsync + + if num_snap != 0 : + if len(str) != 0: + str += "\n" + if num_snap == 1: + str += _("1 snapshot will be deleted.") + else: + str += _("%d snapshots will be deleted.") % num_snap + + summary.set_text(str) + response = confirm.run() + if response != 2: + return + else: + glib.idle_add(self.__init_delete) + return + + def __init_scan(self): + self.snapscanner = ScanSnapshots() + self.pulsedialog.show() + self.snapscanner.start() + glib.timeout_add(100, self.__monitor_scan) + return False + + def __init_delete(self): + self.snapdeleter = DeleteSnapshots(self.backuptodelete) + # If there's more than a few snapshots, pop up + # a progress bar. + if len(self.backuptodelete) > 3: + self.progressbar.set_fraction(0.0) + self.progressdialog.show() + self.snapdeleter.start() + glib.timeout_add(300, self.__monitor_deletion) + return False + + def __monitor_scan(self): + if self.snapscanner.isAlive() == True: + self.xml.get_widget("pulsebar").pulse() + return True + else: + self.pulsedialog.hide() + if self.snapscanner.errors: + details = "" + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Some snapshots could not be read")) + dialog.connect("response", + self.__on_errordialog_response) + for error in self.snapscanner.errors: + details = details + error + dialog.format_secondary_text(details) + dialog.show() + self.__on_filterentry_changed(None) + return False + + def __monitor_deletion(self): + if self.snapdeleter.isAlive() == True: + self.progressbar.set_fraction(self.snapdeleter.progress) + return True + else: + self.progressdialog.hide() + self.progressbar.set_fraction(1.0) + self.progressdialog.hide() + if self.snapdeleter.errors: + details = "" + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Some snapshots could not be deleted")) + dialog.connect("response", + self.__on_errordialog_response) + for error in self.snapdeleter.errors: + details = details + error + dialog.format_secondary_text(details) + dialog.show() + # If we didn't shortcircut straight to the delete confirmation + # dialog then the main dialog is visible so we rebuild the list + # view. + if len(self.shortcircuit) == 0: + self.__refresh_view() + else: + gtk.main_quit() + return False + + def __refresh_view(self): + self.liststorefs.clear() + glib.idle_add(self.__init_scan) + self.backuptodelete = [] + + def __add_selection(self, treemodel, path, iter): + snapshot = treemodel.get(iter, 6)[0] + self.backuptodelete.append(snapshot) + + def __on_confirmcancel_clicked(self, widget): + widget.get_toplevel().hide() + widget.get_toplevel().response(1) + + def __on_confirmdelete_clicked(self, widget): + widget.get_toplevel().hide() + widget.get_toplevel().response(2) + + def __on_errordialog_response(self, widget, responseid): + widget.hide() + +class ScanSnapshots(threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + self.errors = [] + self.datasets = zfs.Datasets() + self.snapshots = [] + self.rsynced_fs = [] + self.rsynced_backups = [] + + def run(self): + self.mounts = self.__get_fs_mountpoints() + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + self.__get_rsync_backups () + self.rescan() + + def __get_rsync_backups (self): + # get rsync backup dir + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsyncDir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + if not os.path.exists(self.rsyncDir): + return + + rootBackupDirs = [] + + for root, dirs, files in os.walk(self.rsyncDir): + if '.time-slider' in dirs: + dirs.remove('.time-slider') + backupDir = os.path.join(root, rsyncsmf.RSYNCDIRSUFFIX) + if os.path.exists(backupDir): + insort(rootBackupDirs, os.path.abspath(backupDir)) + + for dirName in rootBackupDirs: + os.chdir(dirName) + for d in os.listdir(dirName): + if os.path.isdir(d) and not os.path.islink(d): + s1 = dirName.split ("%s/" % self.rsyncDir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + fs = s2[0] + + rb = RsyncBackup ("%s/%s" %(dirName, d), + self.rsyncDir, + fs, + d, + os.stat(d).st_mtime) + self.rsynced_backups.append (rb) + + def __get_fs_mountpoints(self): + """Returns a dictionary mapping: + {filesystem : mountpoint}""" + result = {} + for filesys,mountpoint in self.datasets.list_filesystems(): + result[filesys] = mountpoint + return result + + def rescan(self): + cloned = self.datasets.list_cloned_snapshots() + self.snapshots = [] + snaplist = self.datasets.list_snapshots() + for snapname,snaptime in snaplist: + # Filter out snapshots that are the root + # of cloned filesystems or volumes + try: + cloned.index(snapname) + except ValueError: + snapshot = zfs.Snapshot(snapname, snaptime) + self.snapshots.append(snapshot) + +class DeleteSnapshots(threading.Thread): + + def __init__(self, snapshots): + threading.Thread.__init__(self) + self.backuptodelete = snapshots + self.started = False + self.completed = False + self.progress = 0.0 + self.errors = [] + + def run(self): + deleted = 0 + self.started = True + total = len(self.backuptodelete) + for backup in self.backuptodelete: + # The backup could have expired and been automatically + # destroyed since the user selected it. Check that it + # still exists before attempting to delete it. If it + # doesn't exist just silently ignore it. + if backup.exists(): + try: + backup.destroy () + except RuntimeError, inst: + self.errors.append(str(inst)) + deleted += 1 + self.progress = deleted / (total * 1.0) + self.completed = True + +def main(argv): + try: + opts,args = getopt.getopt(sys.argv[1:], "", []) + except getopt.GetoptError: + sys.exit(2) + rbacp = RBACprofile() + if os.geteuid() == 0: + if len(args) > 0: + manager = DeleteSnapManager(args) + else: + manager = DeleteSnapManager() + gtk.gdk.threads_enter() + glib.idle_add(manager.initialise_view) + gtk.main() + gtk.gdk.threads_leave() + elif os.path.exists(argv) and os.path.exists("/usr/bin/gksu"): + # Run via gksu, which will prompt for the root password + newargs = ["gksu", argv] + for arg in args: + newargs.append(arg) + os.execv("/usr/bin/gksu", newargs); + # Shouldn't reach this point + sys.exit(1) + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Insufficient Priviliges")) + dialog.format_secondary_text(_("Snapshot deletion requires " + "administrative privileges to run. " + "You have not been assigned the necessary" + "administrative priviliges." + "\n\nConsult your system administrator ")) + dialog.run() + print argv + "is not a valid executable path" + sys.exit(1) diff --git a/usr/share/time-slider/lib/time_slider/fileversion.py b/usr/share/time-slider/lib/time_slider/fileversion.py new file mode 100755 index 0000000..86a2584 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/fileversion.py @@ -0,0 +1,328 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import time +import getopt +import os +import gio +import sys +import threading +import subprocess +import string +import gnomevfs +import gnome.ui +#import traceback + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import gobject +except: + sys.exit(1) + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +KILOBYTES = 1024.0 +MEGABYTES = KILOBYTES*1024 +GIGABYTES = MEGABYTES*1024 +TERABYTES = GIGABYTES*1024 + +class File: + displayTemplates = [ + (TERABYTES, '%0.1f TB'), + (GIGABYTES, '%0.1f GB'), + (MEGABYTES, '%0.1f MB'), + (KILOBYTES, '%0.1f KB'), + (0, '%0.1f B'),] + + def __init__(self, path): + self.path = path + self.file = gio.File (path) + try: + self.info = self.file.query_info ("*") + self.exist = True + except gio.Error: + self.exist = False + + def get_icon (self): + #try thumnailer first + icon_factory = gnome.ui.ThumbnailFactory(gnome.ui.THUMBNAIL_SIZE_NORMAL) + mtime = os.path.getmtime(self.path) + uri = gnomevfs.make_uri_from_input(self.path) + thumb = icon_factory.lookup (uri, mtime) + if thumb: + return gtk.gdk.pixbuf_new_from_file (thumb) + thumb = icon_factory.generate_thumbnail (uri, self.info.get_content_type()) + if thumb: + icon_factory.save_thumbnail (thumb, uri, mtime) + return thumb + #fallback get the themed icon + return gtk.icon_theme_get_default().choose_icon (self.info.get_icon().get_property ("names"), 48, gtk.ICON_LOOKUP_USE_BUILTIN).load_icon () + + def get_size (self): + amount = self.info.get_size () + for treshold, template in self.displayTemplates: + if amount > treshold: + if treshold: + amount = amount /treshold + return "%s (%d bytes)" % (template % amount, self.info.get_size ()) + return "%d byte" % amount + + def add_if_unique (self, versions): + found = False + for file in versions: + if int (file.info.get_modification_time ()) == int (self.info.get_modification_time ()): + found = True + if not found: + versions.append (self) + return True + return False + + def get_mime_type (self): + return gnomevfs.get_mime_type(gnomevfs.make_uri_from_input(self.path)) + + +( COLUMN_ICON, + COLUMN_NAME, + COLUMN_STRING_DATE, + COLUMN_DATE, + COLUMN_SIZE +) = range (5) + + + +class FileVersionWindow: + meld_hint_displayed = False + + def __init__(self, snap_path, file): + self.snap_path = snap_path + self.filename = file + self.xml = gtk.glade.XML("%s/../../glade/time-slider-version.glade" \ + % (os.path.dirname(__file__))) + self.window = self.xml.get_widget("toplevel") + self.progress = self.xml.get_widget("progress") + self.version_label = self.xml.get_widget("num_versions_label") + # signal dictionary + dic = {"on_toplevel_delete_event": self.exit3 , + "on_close_clicked": self.exit , + "on_compare_button_clicked": self.on_compare_button_clicked, + "on_current_file_button_clicked": self.on_current_file_button_clicked, + "on_treeview_row_activated": self.on_treeview_row_activated, + "on_treeview_cursor_changed": self.on_treeview_cursor_changed} + self.xml.signal_autoconnect(dic) + + self.filename_label = self.xml.get_widget("filename_label") + self.size_label = self.xml.get_widget("size_label") + self.date_label = self.xml.get_widget("date_label") + self.older_versions_label = self.xml.get_widget("older_versions_label") + self.compare_button = self.xml.get_widget("compare_button") + self.button_init = False + + self.window.show () + + self.file = File (file) + self.filename_label.set_text (self.file.info.get_name ()) + self.size_label.set_text (self.file.get_size ()) + self.date_label.set_text (time.strftime ("%d/%m/%y %Hh%Ms%S", time.localtime(self.file.info.get_modification_time ()))) + self.xml.get_widget("icon_image").set_from_pixbuf (self.file.get_icon ()) + + self.treeview = self.xml.get_widget("treeview") + self.model = gtk.ListStore(gtk.gdk.Pixbuf, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING) + + self.treeview.set_model (self.model) + self.__add_columns (self.treeview) + + self.scanner = VersionScanner (self) + self.scanner.start() + + def __add_columns(self, treeview): + model = treeview.get_model() + + renderer = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn('Icon', renderer, pixbuf=COLUMN_ICON) + treeview.append_column(column) + + self.date_column = gtk.TreeViewColumn('Last Modified Date', gtk.CellRendererText(), + text=COLUMN_STRING_DATE) + self.date_column.set_sort_column_id(COLUMN_DATE) + treeview.append_column(self.date_column) + + # column for description + column = gtk.TreeViewColumn('Size', gtk.CellRendererText(), + text=COLUMN_SIZE) + column.set_sort_column_id(COLUMN_SIZE) + treeview.append_column(column) + + def add_file (self, file): + iter = self.model.append () + self.model.set (iter, + COLUMN_ICON, file.get_icon (), + COLUMN_NAME, file.path, + COLUMN_STRING_DATE, time.strftime ("%d/%m/%y %Hh%Ms%S", time.localtime(file.info.get_modification_time ())), + COLUMN_DATE, file.info.get_modification_time (), + COLUMN_SIZE, file.get_size ()) + + def exit3 (self, blah, blih): + self.exit (self) + + def exit (self, blah): + self.scanner.join () + gtk.main_quit () + + def on_current_file_button_clicked (self, widget): + application = gnomevfs.mime_get_default_application (gnomevfs.get_mime_type(gnomevfs.make_uri_from_input(self.filename))) + if application: + subprocess.Popen (str.split (application[2]) + [self.filename]) + + def on_treeview_row_activated (self, treeview, path, column): + (model, iter) = treeview.get_selection ().get_selected () + filename = model.get (iter, 1)[0] + application = gnomevfs.mime_get_default_application (gnomevfs.get_mime_type(gnomevfs.make_uri_from_input(filename))) + if application: + subprocess.Popen (str.split (application[2]) + [filename]) + + def on_treeview_cursor_changed (self, treeview): + if not self.button_init: + self.button_init = True + if self.file.get_mime_type ().find ("text") != -1 : + self.compare_button.set_sensitive (True) + + def on_compare_button_clicked (self, widget): + (model, iter) = self.treeview.get_selection ().get_selected () + filename = model.get (iter, 1)[0] + if os.path.exists ("/usr/bin/meld"): + subprocess.Popen (["/usr/bin/meld",self.filename, filename]) + else: + if not self.meld_hint_displayed: + dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, _("Hint")) + dialog.set_title (_("Hint")) + dialog.format_secondary_text(_("Installing the optional meld package will enhance the file comparison visualization")) + dialog.run () + dialog.destroy () + self.meld_hint_displayed = True + p1 = subprocess.Popen(["/usr/bin/diff", "-u", self.filename, filename], stdout=subprocess.PIPE) + p2 = subprocess.Popen(str.split ("/usr/bin/zenity --text-info --editable"), stdin=p1.stdout, stdout=subprocess.PIPE) + + +class VersionScanner(threading.Thread): + + def __init__(self, window): + self.w = window + self._stopevent = threading.Event() + threading.Thread.__init__(self) + + def run(self): + l = self.w.snap_path.split (".zfs") + path_before_snap = l[0] + l = self.w.filename.split (path_before_snap) + path_after_snap = l[1] + snap_path = "%s.zfs/snapshot/" % path_before_snap; + dirs = os.listdir(snap_path) + + num_dirs = len(dirs) + current_dir = 1 + + gobject.idle_add (self.w.progress.set_pulse_step, (1.0 / num_dirs)) + gobject.idle_add (self.w.progress.set_text, ("Scanning for older versions (%d/%d)" % (current_dir, num_dirs))) + + versions = [File (self.w.filename)] + + for dir in dirs: + if not self._stopevent.isSet (): + file = File ("%s%s/%s" % (snap_path, dir, path_after_snap)) + if file.exist : + if file.add_if_unique(versions): + gobject.idle_add (self.w.add_file, file) + fraction = self.w.progress.get_fraction () + fraction += self.w.progress.get_pulse_step () + if fraction > 1: + fraction = 1 + + gobject.idle_add (self.w.progress.set_fraction, fraction) + current_dir += 1 + gobject.idle_add (self.w.progress.set_text, "Scanning for older versions (%d/%d)" % (current_dir, num_dirs)) + else: + return None + + gobject.idle_add(self.w.progress.hide) + gobject.idle_add(self.w.older_versions_label.set_markup , "Older Versions (%d) " % (len(versions) - 1)) + # sort by date + gobject.idle_add(self.w.date_column.emit, "clicked") + gobject.idle_add(self.w.date_column.emit, "clicked") + + def join(self, timeout=None): + self._stopevent.set () + threading.Thread.join(self, timeout) + +def main(argv): + try: + opts, args = getopt.getopt(sys.argv[1:], "", []) + except getopt.GetoptError: + sys.exit(2) + if len(args) != 2: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Invalid arguments count.")) + dialog.set_title ("Error") + dialog.format_secondary_text(_("Version explorer requires" + " 2 arguments :\n- The path of the " + "root snapshot directory.\n" + "- The filename to explore.")) + dialog.run() + sys.exit (2) + + window = FileVersionWindow(args[0], args[1]) + gtk.gdk.threads_enter() + gtk.main() + gtk.gdk.threads_leave() diff --git a/usr/share/time-slider/lib/time_slider/rbac.py b/usr/share/time-slider/lib/time_slider/rbac.py new file mode 100755 index 0000000..1995205 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/rbac.py @@ -0,0 +1,118 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import pwd + +import util + +class RBACprofile: + + def __init__(self, name = None): + # Filtering through the pwd module is beneficial because + # it will raise a KeyError exception for an invalid + # name argument + if name == None: + euid = os.geteuid() + pwnam = pwd.getpwuid(euid) + self.uid = euid + self.name = pwnam[0] + else: + pwnam = pwd.getpwnam(name) + self.name = pwnam[0] + self.uid = pwnam[2] + + self.profiles = self.get_profiles() + self.auths = self.get_auths() + + def get_profiles(self): + cmd = ["/usr/bin/profiles", self.name] + profiles = [] + outdata,errdata = util.run_command(cmd) + for line in outdata.split('\n'): + if line.isspace(): + continue + else: + try: + line.index(self.name + " :") + except ValueError: + profiles.append(line.strip()) + # Remove "All" because it's (seemingly) meaningless + try: + profiles.remove("All") + except ValueError: + return profiles + return profiles + + def get_auths(self): + cmd = ["/usr/bin/auths", self.name] + auths = [] + outdata,errdata = util.run_command(cmd) + auths = outdata.rstrip().split(",") + return auths + + def has_profile(self, profile): + # root is all powerful + if self.uid == 0: + return True + try: + self.profiles.index(profile) + except ValueError: + return False + return True + + def has_auth(self, auth): + """ Checks the user's authorisations to see if "auth" is + assigned to the user. Recursively searches higher up + for glob matching eg. solaris.network.hosts.read -> + solaris.network.hosts.* -> solaris.network.* -> + solaris.*, until a valid authorisation is found. + Returns True if user has the "auth" authorisation, + False otherwise""" + try: + self.auths.index(auth) + return True + except ValueError: + subpattern = auth.rsplit(".", 1) + # If there are still more "."s in the string + if subpattern[0] != auth: + # Try using the glob pattern if auth is not + # already a glob pattern eg. solaris.device.* + if subpattern[1] != "*": + try: + self.auths.index("%s.*" % subpattern[0]) + return True + except ValueError: + pass + # Strip another "." off the auth and carry on searching + subsearch = subpattern[0].rsplit(".", 1) + if subsearch[0] != subpattern[0]: + return self.has_auth("%s.*" % subsearch[0]) + return False + +if __name__ == "__main__": + rbac = RBACprofile() + print rbac.name + print rbac.uid + print rbac.profiles + print rbac.auths + diff --git a/usr/share/time-slider/lib/time_slider/setupgui.py b/usr/share/time-slider/lib/time_slider/setupgui.py new file mode 100755 index 0000000..b3a37b6 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/setupgui.py @@ -0,0 +1,1322 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +import os +import subprocess +import threading +import util +import smf +from autosnapsmf import enable_default_schedules, disable_default_schedules + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import rsyncsmf + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) + +import glib +import gobject +import gio +import dbus +import dbus.service +import dbus.mainloop +import dbus.mainloop.glib +import dbussvc + + +# This is the rough guess ratio used for rsync backup device size +# vs. the total size of the pools it's expected to backup. +RSYNCTARGETRATIO = 2 + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from timeslidersmf import TimeSliderSMF +from rbac import RBACprofile + + +class FilesystemIntention: + + def __init__(self, name, selected, inherited): + self.name = name + self.selected = selected + self.inherited = inherited + + def __str__(self): + return_string = "Filesystem name: " + self.name + \ + "\n\tSelected: " + str(self.selected) + \ + "\n\tInherited: " + str(self.inherited) + return return_string + + def __eq__(self, other): + if self.name != other.name: + return False + if self.inherited and other.inherited: + return True + elif not self.inherited and other.inherited: + return False + if (self.selected == other.selected) and \ + (self.inherited == other.inherited): + return True + else: + return False + +class SetupManager: + + def __init__(self, execpath): + self._execPath = execpath + self._datasets = zfs.Datasets() + self._xml = gtk.glade.XML("%s/../../glade/time-slider-setup.glade" \ + % (os.path.dirname(__file__))) + + # Tell dbus to use the gobject mainloop for async ops + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + dbus.mainloop.glib.threads_init() + + # Register a bus name with the system dbus daemon + systemBus = dbus.SystemBus() + busName = dbus.service.BusName("org.opensolaris.TimeSlider.config", + systemBus) + self._dbus = dbussvc.Config(systemBus, + '/org/opensolaris/TimeSlider/config') + # Used later to trigger a D-Bus notification of select configuration + # changes made + self._configNotify = False + + # These variables record the initial UI state which are used + # later to compare against the UI state when the OK or Cancel + # button is clicked and apply the minimum set of necessary + # configuration changes. Prevents minor changes taking ages + # to be applied by the GUI. + self._initialEnabledState = None + self._initialRsyncState = None + self._initialRsyncTargetDir = None + self._initialCleanupLevel = None + self._initialCustomSelection = False + self._initialSnapStateDic = {} + self._initialRsyncStateDic = {} + self._initialFsIntentDic = {} + self._initialRsyncIntentDic = {} + + # Currently selected rsync backup device via the GUI. + self._newRsyncTargetDir = None + # Used to store GUI filesystem selection state and the + # set of intended properties to apply to zfs filesystems. + self._snapStateDic = {} + self._rsyncStateDic = {} + self._fsIntentDic = {} + self._rsyncIntentDic = {} + # Dictionary that maps device ID numbers to zfs filesystem objects + self._fsDevices = {} + + topLevel = self._xml.get_widget("toplevel") + self._pulseDialog = self._xml.get_widget("pulsedialog") + self._pulseDialog.set_transient_for(topLevel) + + # gio.VolumeMonitor reference + self._vm = gio.volume_monitor_get() + self._vm.connect("mount-added", self._mount_added) + self._vm.connect("mount-removed" , self._mount_removed) + + self._fsListStore = gtk.ListStore(bool, + bool, + str, + str, + gobject.TYPE_PYOBJECT) + filesystems = self._datasets.list_filesystems() + for fsname,fsmountpoint in filesystems: + if (fsmountpoint == "legacy"): + mountpoint = _("Legacy") + else: + mountpoint = fsmountpoint + fs = zfs.Filesystem(fsname, fsmountpoint) + # Note that we don't deal with legacy mountpoints. + if fsmountpoint != "legacy" and fs.is_mounted(): + self._fsDevices[os.stat(fsmountpoint).st_dev] = fs + snap = fs.get_auto_snap() + rsyncstr = fs.get_user_property(rsyncsmf.RSYNCFSTAG) + if rsyncstr == "true": + rsync = True + else: + rsync = False + # Rsync is only performed on snapshotted filesystems. + # So treat as False if rsync is set to true independently + self._fsListStore.append([snap, snap & rsync, + mountpoint, fs.name, fs]) + self._initialSnapStateDic[fs.name] = snap + self._initialRsyncStateDic[fs.name] = snap & rsync + del filesystems + + for fsname in self._initialSnapStateDic: + self._refine_filesys_actions(fsname, + self._initialSnapStateDic, + self._initialFsIntentDic) + self._refine_filesys_actions(fsname, + self._initialRsyncStateDic, + self._initialRsyncIntentDic) + + self._fsTreeView = self._xml.get_widget("fstreeview") + self._fsTreeView.set_sensitive(False) + self._fsTreeView.set_size_request(10, 200) + + self._fsTreeView.set_model(self._fsListStore) + + cell0 = gtk.CellRendererToggle() + cell1 = gtk.CellRendererToggle() + cell2 = gtk.CellRendererText() + cell3 = gtk.CellRendererText() + + radioColumn = gtk.TreeViewColumn(_("Select"), + cell0, active=0) + self._fsTreeView.insert_column(radioColumn, 0) + + self._rsyncRadioColumn = gtk.TreeViewColumn(_("Replicate"), + cell1, active=1) + nameColumn = gtk.TreeViewColumn(_("Mount Point"), + cell2, text=2) + self._fsTreeView.insert_column(nameColumn, 2) + mountPointColumn = gtk.TreeViewColumn(_("File System Name"), + cell3, text=3) + self._fsTreeView.insert_column(mountPointColumn, 3) + cell0.connect('toggled', self._row_toggled) + cell1.connect('toggled', self._rsync_cell_toggled) + advancedBox = self._xml.get_widget("advancedbox") + advancedBox.connect('unmap', self._advancedbox_unmap) + + self._rsyncSMF = rsyncsmf.RsyncSMF("%s:rsync" \ + %(plugin.PLUGINBASEFMRI)) + state = self._rsyncSMF.get_service_state() + self._initialRsyncTargetDir = self._rsyncSMF.get_target_dir() + # Check for the default, unset value of "" from SMF. + if self._initialRsyncTargetDir == '""': + self._initialRsyncTargetDir = '' + self._newRsyncTargetDir = self._initialRsyncTargetDir + self._smfTargetKey = self._rsyncSMF.get_target_key() + self._newRsyncTargetSelected = False + sys,self._nodeName,rel,ver,arch = os.uname() + + # Model columns: + # 0 Themed icon list (python list) + # 1 device root + # 2 volume name + # 3 Is gio.Mount device + # 4 Is separator (for comboBox separator rendering) + self._rsyncStore = gtk.ListStore(gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN) + self._rsyncCombo = self._xml.get_widget("rsyncdevcombo") + mounts = self._vm.get_mounts() + for mount in mounts: + self._mount_added(self._vm, mount) + if len(mounts) > 0: + # Add a separator + self._rsyncStore.append((None, None, None, None, True)) + del mounts + + if len(self._newRsyncTargetDir) == 0: + self._rsyncStore.append((['folder'], + _("(None)"), + '', + False, + False)) + # Add a separator + self._rsyncStore.append((None, None, None, None, True)) + self._rsyncStore.append((None, _("Other..."), "Other", False, False)) + self._iconCell = gtk.CellRendererPixbuf() + self._nameCell = gtk.CellRendererText() + self._rsyncCombo.clear() + self._rsyncCombo.pack_start(self._iconCell, False) + self._rsyncCombo.set_cell_data_func(self._iconCell, + self._icon_cell_render) + self._rsyncCombo.pack_end(self._nameCell) + self._rsyncCombo.set_attributes(self._nameCell, text=1) + self._rsyncCombo.set_row_separator_func(self._row_separator) + self._rsyncCombo.set_model(self._rsyncStore) + self._rsyncCombo.connect("changed", self._rsync_combo_changed) + # Force selection of currently configured device + self._rsync_dev_selected(self._newRsyncTargetDir) + + # signal dictionary + dic = {"on_ok_clicked" : self._on_ok_clicked, + "on_cancel_clicked" : gtk.main_quit, + "on_snapshotmanager_delete_event" : gtk.main_quit, + "on_enablebutton_toggled" : self._on_enablebutton_toggled, + "on_rsyncbutton_toggled" : self._on_rsyncbutton_toggled, + "on_defaultfsradio_toggled" : self._on_defaultfsradio_toggled, + "on_selectfsradio_toggled" : self._on_selectfsradio_toggled, + "on_deletesnapshots_clicked" : self._on_deletesnapshots_clicked} + self._xml.signal_autoconnect(dic) + + if state != "disabled": + self._rsyncEnabled = True + self._xml.get_widget("rsyncbutton").set_active(True) + self._initialRsyncState = True + else: + self._rsyncEnabled = False + self._rsyncCombo.set_sensitive(False) + self._initialRsyncState = False + + # Initialise SMF service instance state. + try: + self._sliderSMF = TimeSliderSMF() + except RuntimeError,message: + self._xml.get_widget("toplevel").set_sensitive(False) + dialog = gtk.MessageDialog(self._xml.get_widget("toplevel"), + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot manager service error")) + dialog.format_secondary_text(_("The snapshot manager service does " + "not appear to be installed on this " + "system." + "\n\nSee the svcs(1) man page for more " + "information." + "\n\nDetails:\n%s")%(message)) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + + if self._sliderSMF.svcstate == "disabled": + self._xml.get_widget("enablebutton").set_active(False) + self._initialEnabledState = False + elif self._sliderSMF.svcstate == "offline": + self._xml.get_widget("toplevel").set_sensitive(False) + errors = ''.join("%s\n" % (error) for error in \ + self._sliderSMF.find_dependency_errors()) + dialog = gtk.MessageDialog(self._xml.get_widget("toplevel"), + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot manager service dependency error")) + dialog.format_secondary_text(_("The snapshot manager service has " + "been placed offline due to a dependency " + "problem. The following dependency problems " + "were found:\n\n%s\n\nRun \"svcs -xv\" from " + "a command prompt for more information about " + "these dependency problems.") % errors) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + elif self._sliderSMF.svcstate == "maintenance": + self._xml.get_widget("toplevel").set_sensitive(False) + dialog = gtk.MessageDialog(self._xml.get_widget("toplevel"), + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot manager service error")) + dialog.format_secondary_text(_("The snapshot manager service has " + "encountered a problem and has been " + "disabled until the problem is fixed." + "\n\nSee the svcs(1) man page for more " + "information.")) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + else: + # FIXME: Check transitional states + self._xml.get_widget("enablebutton").set_active(True) + self._initialEnabledState = True + + + # Emit a toggled signal so that the initial GUI state is consistent + self._xml.get_widget("enablebutton").emit("toggled") + # Check the snapshotting policy (UserData (default), or Custom) + self._initialCustomSelection = self._sliderSMF.is_custom_selection() + if self._initialCustomSelection == True: + self._xml.get_widget("selectfsradio").set_active(True) + # Show the advanced controls so the user can see the + # customised configuration. + if self._sliderSMF.svcstate != "disabled": + self._xml.get_widget("expander").set_expanded(True) + else: # "false" or any other non "true" value + self._xml.get_widget("defaultfsradio").set_active(True) + + # Set the cleanup threshhold value + spinButton = self._xml.get_widget("capspinbutton") + critLevel = self._sliderSMF.get_cleanup_level("critical") + warnLevel = self._sliderSMF.get_cleanup_level("warning") + + # Force the warning level to something practical + # on the lower end, and make it no greater than the + # critical level specified in the SVC instance. + spinButton.set_range(70, critLevel) + self._initialCleanupLevel = warnLevel + if warnLevel > 70: + spinButton.set_value(warnLevel) + else: + spinButton.set_value(70) + + def _icon_cell_render(self, celllayout, cell, model, iter): + iconList = self._rsyncStore.get_value(iter, 0) + if iconList != None: + gicon = gio.ThemedIcon(iconList) + cell.set_property("gicon", gicon) + else: + root = self._rsyncStore.get_value(iter, 2) + if root == "Other": + cell.set_property("gicon", None) + + def _row_separator(self, model, iter): + return model.get_value(iter, 4) + + def _mount_added(self, volume_monitor, mount): + icon = mount.get_icon() + iconList = icon.get_names() + if iconList == None: + iconList = ['drive-harddisk', 'drive'] + root = mount.get_root() + path = root.get_path() + mountName = mount.get_name() + volume = mount.get_volume() + if volume == None: + volName = mount.get_name() + if volName == None: + volName = os.path.split(path)[1] + else: + volName = volume.get_name() + + # Check to see if there is at least one gio.Mount device already + # in the ListStore. If not, then we also need to add a separator + # row. + iter = self._rsyncStore.get_iter_first() + if iter and self._rsyncStore.get_value(iter, 3) == False: + self._rsyncStore.insert(0, (None, None, None, None, True)) + + self._rsyncStore.insert(0, (iconList, volName, path, True, False)) + # If this happens to be the already configured backup device + # and the user hasn't tried to change device yet, auto select + # it. + if self._initialRsyncTargetDir == self._newRsyncTargetDir: + if self._validate_rsync_target(path) == True: + self._rsyncCombo.set_active(0) + + def _mount_removed(self, volume_monitor, mount): + root = mount.get_root() + path = root.get_path() + iter = self._rsyncStore.get_iter_first() + mountIter = None + numMounts = 0 + # Search gio.Mount devices + while iter != None and \ + self._rsyncStore.get_value(iter, 3) == True: + numMounts += 1 + compPath = self._rsyncStore.get_value(iter, 2) + if compPath == path: + mountIter = iter + break + else: + iter = self._rsyncStore.iter_next(iter) + if mountIter != None: + if numMounts == 1: + # Need to remove the separator also since + # there will be no more gio.Mount devices + # shown in the combo box + sepIter = self._rsyncStore.iter_next(mountIter) + if self._rsyncStore.get_value(sepIter, 4) == True: + self._rsyncStore.remove(sepIter) + self._rsyncStore.remove(mountIter) + iter = self._rsyncStore.get_iter_first() + # Insert a custom folder if none exists already + if self._rsyncStore.get_value(iter, 2) == "Other": + path = self._initialRsyncTargetDir + length = len(path) + if length > 1: + name = os.path.split(path)[1] + elif length == 1: + name = path + else: # Indicates path is unset: '' + name = _("(None)") + iter = self._rsyncStore.insert_before(iter, + (None, + None, + None, + None, + True)) + iter = self._rsyncStore.insert_before(iter, + (['folder'], + name, + path, + False, + False)) + self._rsyncCombo.set_active_iter(iter) + + def _monitor_setup(self, pulseBar): + if self._enabler.isAlive() == True: + pulseBar.pulse() + return True + else: + gtk.main_quit() + + def _row_toggled(self, renderer, path): + model = self._fsTreeView.get_model() + iter = model.get_iter(path) + state = renderer.get_active() + if state == False: + self._fsListStore.set_value(iter, 0, True) + else: + self._fsListStore.set_value(iter, 0, False) + self._fsListStore.set_value(iter, 1, False) + + def _rsync_cell_toggled(self, renderer, path): + model = self._fsTreeView.get_model() + iter = model.get_iter(path) + state = renderer.get_active() + rowstate = self._fsListStore.get_value(iter, 0) + if rowstate == True: + if state == False: + self._fsListStore.set_value(iter, 1, True) + else: + self._fsListStore.set_value(iter, 1, False) + + def _rsync_config_error(self, msg): + topLevel = self._xml.get_widget("toplevel") + dialog = gtk.MessageDialog(topLevel, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Unsuitable Backup Location")) + dialog.format_secondary_text(msg) + dialog.set_icon_name("time-slider-setup") + dialog.run() + dialog.hide() + return + + def _rsync_dev_selected(self, path): + iter = self._rsyncStore.get_iter_first() + while iter != None: + # Break out when we hit a non gio.Mount device + if self._rsyncStore.get_value(iter, 3) == False: + break + compPath = self._rsyncStore.get_value(iter, 2) + if compPath == path: + self._rsyncCombo.set_active_iter(iter) + self._newRsyncTargetDir = path + return + else: + iter = self._rsyncStore.iter_next(iter) + + # Not one of the shortcut RMM devices, so it's + # some other path on the filesystem. + # iter may be pointing at a separator. Increment + # to next row iter if so. + if self._rsyncStore.get_value(iter, 4) == True: + iter = self._rsyncStore.iter_next(iter) + + if iter != None: + if len(path) > 1: + name = os.path.split(path)[1] + elif len(path) == 1: + name = path + else: # Indicates path is unset: '' + name = _("(None)") + # Could be either the custom folder selection + # row or the "Other" row if the custom row + # was not created. If "Other" then create the + # custom row and separator now at this position + if self._rsyncStore.get_value(iter, 2) == "Other": + iter = self._rsyncStore.insert_before(iter, + (None, + None, + None, + None, + True)) + iter = self._rsyncStore.insert_before(iter, + (['folder'], + name, + path, + False, + False)) + else: + self._rsyncStore.set(iter, + 1, name, + 2, path) + self._rsyncCombo.set_active_iter(iter) + self._newRsyncTargetDir = path + + def _rsync_combo_changed(self, combobox): + newIter = combobox.get_active_iter() + if newIter != None: + root = self._rsyncStore.get_value(newIter, 2) + if root != "Other": + self._newRsyncTargetDir = root + else: + msg = _("Select A Back Up Device") + fileDialog = \ + gtk.FileChooserDialog( + msg, + self._xml.get_widget("toplevel"), + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL, + gtk.STOCK_OK,gtk.RESPONSE_OK), + None) + self._rsyncCombo.set_sensitive(False) + response = fileDialog.run() + fileDialog.hide() + if response == gtk.RESPONSE_OK: + gFile = fileDialog.get_file() + self._rsync_dev_selected(gFile.get_path()) + else: + self._rsync_dev_selected(self._newRsyncTargetDir) + self._rsyncCombo.set_sensitive(True) + + def _rsync_size_warning(self, zpools, zpoolSize, + rsyncTarget, targetSize): + # Using decimal "GB" instead of binary "GiB" + KB = 1000 + MB = 1000 * KB + GB = 1000 * MB + TB = 1000 * GB + + suggestedSize = RSYNCTARGETRATIO * zpoolSize + if suggestedSize > TB: + sizeStr = "%.1f TB" % round(suggestedSize / float(TB), 1) + elif suggestedSize > GB: + sizeStr = "%.1f GB" % round(suggestedSize / float(GB), 1) + else: + sizeStr = "%.1f MB" % round(suggestedSize / float(MB), 1) + + if targetSize > TB: + targetStr = "%.1f TB" % round(targetSize / float(TB), 1) + elif targetSize > GB: + targetStr = "%.1f GB" % round(targetSize / float(GB), 1) + else: + targetStr = "%.1f MB" % round(targetSize / float(MB), 1) + + + msg = _("Time Slider suggests a device with a capacity of at " + "least %s.\n" + "The device: \'%s\'\nonly has %s\n" + "Do you want to use it anyway?") \ + % (sizeStr, rsyncTarget, targetStr) + + topLevel = self._xml.get_widget("toplevel") + dialog = gtk.MessageDialog(topLevel, + 0, + gtk.MESSAGE_QUESTION, + gtk.BUTTONS_YES_NO, + _("Time Slider")) + dialog.set_default_response(gtk.RESPONSE_NO) + dialog.set_transient_for(topLevel) + dialog.set_markup(msg) + dialog.set_icon_name("time-slider-setup") + + response = dialog.run() + dialog.hide() + if response == gtk.RESPONSE_YES: + return True + else: + return False + + def _check_rsync_config(self): + """ + Checks rsync configuration including, filesystem selection, + target directory validation and capacity checks. + Returns True if everything is OK, otherwise False. + Pops up blocking error dialogs to notify users of error + conditions before returning. + """ + def _get_mount_point(path): + if os.path.ismount(path): + return path + else: + return _get_mount_point(abspath(join(path, pardir))) + + if self._rsyncEnabled != True: + return True + + if len(self._newRsyncTargetDir) == 0: + msg = _("No backup device was selected.\n" + "Please select an empty device.") + self._rsync_config_error(msg) + return False + # There's little that can be done if the device is from a + # previous configuration and currently offline. So just + # treat it as being OK based on the assumption that it was + # previously deemed to be OK. + if self._initialRsyncTargetDir == self._newRsyncTargetDir and \ + not os.path.exists(self._newRsyncTargetDir): + return True + # Perform the required validation checks on the + # target directory. + newTargetDir = self._newRsyncTargetDir + + # We require the whole device. So find the enclosing + # mount point and inspect from there. + targetMountPoint = abspath(_get_mount_point(newTargetDir)) + + # Check that it's writable. + f = None + testFile = os.path.join(targetMountPoint, ".ts-test") + try: + f = open(testFile, 'w') + except (OSError, IOError): + msg = _("\'%s\'\n" + "is not writable. The backup device must " + "be writable by the system administrator." + "\n\nPlease use a different device.") \ + % (targetMountPoint) + self._rsync_config_error(msg) + return False + f.close() + + # Try to create a symlink. Rsync requires this to + # do incremental backups and to ensure it's posix like + # enough to correctly set file ownerships and perms. + os.chdir(targetMountPoint) + try: + os.link(testFile, ".ts-test-link") + except OSError: + msg = _("\'%s\'\n" + "contains an incompatible file system. " + "The selected device must have a Unix " + "style file system that supports file " + "linking, such as UFS" + "\n\nPlease use a different device.") \ + % (targetMountPoint) + self._rsync_config_error(msg) + return False + finally: + os.unlink(testFile) + os.unlink(".ts-test-link") + + # Check that selected directory is either empty + # or already preconfigured as a backup target + sys,nodeName,rel,ver,arch = os.uname() + basePath = os.path.join(targetMountPoint, + rsyncsmf.RSYNCDIRPREFIX) + nodePath = os.path.join(basePath, + nodeName) + configPath = os.path.join(basePath, + rsyncsmf.RSYNCCONFIGFILE) + self._newRsyncTargetSelected = True + targetDirKey = None + + contents = os.listdir(targetMountPoint) + os.chdir(targetMountPoint) + + # The only other exception to an empty directory is + # "lost+found". + for item in contents: + if (item != rsyncsmf.RSYNCDIRPREFIX and \ + item != "lost+found") or \ + not os.path.isdir(item) or \ + os.path.islink(item): + msg = _("\'%s\'\n is not an empty device.\n\n" + "Please select an empty device.") \ + % (newTargetDir) + self._rsync_config_error(msg) + return False + + # Validate existing directory structure + if os.path.exists(basePath): + # We only accept a pre-existing directory if + # 1. It has a config key that matches that stored by + # the rsync plugin's SMF configuration + # 2. It has a single subfolder that matches the nodename + # of this system, + + # Check for previous config key + if os.path.exists(configPath): + f = open(configPath, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetDirKey = val.strip() + break + + # Examine anything else in the directory + self._targetSelectionError = None + dirList = [d for d in os.listdir(basePath) if + d != '.rsync-config'] + os.chdir(basePath) + if len(dirList) > 0: + msg = _("\'%s\'\n is not an empty device.\n\n" + "Please select an empty device.") \ + % (newTargetDir) + # No config key or > 1 directory: + # User specified a non empty directory. + if targetDirKey == None or len(dirList) > 1: + self._rsync_config_error(msg) + return False + # Make sure the single item is not a file or symlink. + elif os.path.islink(dirList[0]) or \ + os.path.isfile(dirList[0]): + self._rsync_config_error(msg) + return False + else: + # Has 1 other item and a config key. Other + # item must be a directory and must match the + # system nodename and SMF's key value respectively + # respectively + if dirList[0] != nodeName and \ + targetDirKey != self._smfTargetKey: + msg = _("\'%s\'\n" + "is a Time Slider external backup device " + "that is already in use by another system. " + "Backup devices may not be shared between " + "systems." + "\n\nPlease use a different device.") \ + % (newTargetDir) + self._rsync_config_error(msg) + return False + else: + if dirList[0] == nodeName and \ + targetDirKey != self._smfTargetKey: + # Looks like a device that we previously used, + # but discontinued using in favour of some other + # device. + msg = _("\'%s\' appears to be a a device " + "previously configured for use by this " + "system.\n\nDo you want resume use of " + "this device for backups?") \ + % (newTargetDir) + + topLevel = self._xml.get_widget("toplevel") + dialog = gtk.MessageDialog(topLevel, + 0, + gtk.MESSAGE_QUESTION, + gtk.BUTTONS_YES_NO, + _("Time Slider")) + dialog.set_default_response(gtk.RESPONSE_NO) + dialog.set_transient_for(topLevel) + dialog.set_markup(msg) + dialog.set_icon_name("time-slider-setup") + + response = dialog.run() + dialog.hide() + if response == gtk.RESPONSE_NO: + return False + else: + # Appears to be our own pre-configured directory. + self._newRsyncTargetSelected = False + + # Compare device ID against selected ZFS filesystems + # and their enclosing Zpools. The aim is to avoid + # a vicous circle caused by backing up snapshots onto + # the same pool the snapshots originate from + targetDev = os.stat(newTargetDir).st_dev + try: + fs = self._fsDevices[targetDev] + + # See if the filesystem itself is selected + # and/or any other fileystem on the pool is + # selected. + fsEnabled = self._snapStateDic[fs.name] + if fsEnabled == True: + # Definitely can't use this since it's a + # snapshotted filesystem. + msg = _("\'%s\'\n" + "belongs to the ZFS filesystem \'%s\' " + "which is already selected for " + "regular ZFS snaphots." + "\n\nPlease select a drive " + "not already in use by " + "Time Slider") \ + % (newTargetDir, fs.name) + self._rsync_config_error(msg) + return False + else: + # See if there is anything else on the pool being + # snapshotted + poolName = fs.name.split("/", 1)[0] + for name,mount in self._datasets.list_filesystems(): + if name.find(poolName) == 0: + try: + otherEnabled = self._snapStateDic[name] + radioBtn = self._xml.get_widget("defaultfsradio") + snapAll = radioBtn.get_active() + if snapAll or otherEnabled: + msg = _("\'%s\'\n" + "belongs to the ZFS pool \'%s\' " + "which is already being used " + "to store ZFS snaphots." + "\n\nPlease select a drive " + "not already in use by " + "Time Slider") \ + % (newTargetDir, poolName) + self._rsync_config_error(msg) + return False + except KeyError: + pass + except KeyError: + # No match found - good. + pass + + + # Figure out if there's a reasonable amount of free space to + # store backups. This is a vague guess at best. + allPools = zfs.list_zpools() + snapPools = [] + # FIXME - this is for custom selection. There is a short + # circuit case for default (All) configuration. Don't forget + # to implement this short circuit. + for poolName in allPools: + try: + snapPools.index(poolName) + except ValueError: + pool = zfs.ZPool(poolName) + # FIXME - we should include volumes here but they + # can only be set from the command line, not via + # the GUI, so not crucial. + for fsName,mount in pool.list_filesystems(): + # Don't try to catch exception. The filesystems + # are already populated in self._snapStateDic + enabled = self._snapStateDic[fsName] + if enabled == True: + snapPools.append(poolName) + break + + sumPoolSize = 0 + for poolName in snapPools: + pool = zfs.ZPool(poolName) + # Rough calcualation, but precise enough for + # estimation purposes + sumPoolSize += pool.get_used_size() + sumPoolSize += pool.get_available_size() + + + # Compare with available space on rsync target dir + targetAvail = util.get_available_size(targetMountPoint) + targetUsed = util.get_used_size(targetMountPoint) + targetSum = targetAvail + targetUsed + + # Recommended Minimum: + # At least double the combined size of all pools with + # fileystems selected for backup. Variables include, + # frequency of data changes, how much efficiency rsync + # sacrifices compared to ZFS' block level diff tracking, + # whether compression and/or deduplication are enabled + # on the source pool/fileystem. + # We don't try to make calculations based on individual + # filesystem selection as there are too many unpredictable + # variables to make an estimation of any practical use. + # Let the user figure that out for themselves. + + # The most consistent measurement is to use the sum of + # available and used size on the target fileystem. We + # assume based on previous checks that the target device + # is only being used for rsync backups and therefore the + # used value consists of existing backups and is. Available + # space can be reduced for various reasons including the used + # value increasing or for nfs mounted zfs fileystems, other + # zfs filesystems on the containing pool using up more space. + + + targetPoolRatio = targetSum/float(sumPoolSize) + if (targetPoolRatio < RSYNCTARGETRATIO): + response = self._rsync_size_warning(snapPools, + sumPoolSize, + targetMountPoint, + targetSum) + if response == False: + return False + + self._newRsyncTargetDir = targetMountPoint + return True + + def _on_ok_clicked(self, widget): + # Make sure the dictionaries are empty. + self._fsIntentDic = {} + self._snapStateDic = {} + self._rsyncStateDic = {} + enabled = self._xml.get_widget("enablebutton").get_active() + self._rsyncEnabled = self._xml.get_widget("rsyncbutton").get_active() + if enabled == False: + if self._rsyncEnabled == False and \ + self._initialRsyncState == True: + self._rsyncSMF.disable_service() + if self._initialEnabledState == True: + self._sliderSMF.disable_service() + # Ignore other changes to the snapshot/rsync configuration + # of filesystems. Just broadcast the change and exit. + self._configNotify = True + self.broadcast_changes() + gtk.main_quit() + else: + model = self._fsTreeView.get_model() + snapalldata = self._xml.get_widget("defaultfsradio").get_active() + + if snapalldata == True: + model.foreach(self._set_fs_selection_state, True) + if self._rsyncEnabled == True: + model.foreach(self._set_rsync_selection_state, True) + else: + model.foreach(self._get_fs_selection_state) + model.foreach(self._get_rsync_selection_state) + for fsname in self._snapStateDic: + self._refine_filesys_actions(fsname, + self._snapStateDic, + self._fsIntentDic) + if self._rsyncEnabled == True: + self._refine_filesys_actions(fsname, + self._rsyncStateDic, + self._rsyncIntentDic) + if self._rsyncEnabled and \ + not self._check_rsync_config(): + return + + self._pulseDialog.show() + self._enabler = EnableService(self) + self._enabler.start() + glib.timeout_add(100, + self._monitor_setup, + self._xml.get_widget("pulsebar")) + + def _on_enablebutton_toggled(self, widget): + expander = self._xml.get_widget("expander") + enabled = widget.get_active() + self._xml.get_widget("filesysframe").set_sensitive(enabled) + expander.set_sensitive(enabled) + if (enabled == False): + expander.set_expanded(False) + + def _on_rsyncbutton_toggled(self, widget): + self._rsyncEnabled = widget.get_active() + if self._rsyncEnabled == True: + self._fsTreeView.insert_column(self._rsyncRadioColumn, 1) + self._rsyncCombo.set_sensitive(True) + else: + self._fsTreeView.remove_column(self._rsyncRadioColumn) + self._rsyncCombo.set_sensitive(False) + + def _on_defaultfsradio_toggled(self, widget): + if widget.get_active() == True: + self._xml.get_widget("fstreeview").set_sensitive(False) + + def _on_selectfsradio_toggled(self, widget): + if widget.get_active() == True: + self._xml.get_widget("fstreeview").set_sensitive(True) + + def _advancedbox_unmap(self, widget): + # Auto shrink the window by subtracting the frame's height + # requistion from the window's height requisition + myrequest = widget.size_request() + toplevel = self._xml.get_widget("toplevel") + toprequest = toplevel.size_request() + toplevel.resize(toprequest[0], toprequest[1] - myrequest[1]) + + def _get_fs_selection_state(self, model, path, iter): + fsname = self._fsListStore.get_value(iter, 3) + enabled = self._fsListStore.get_value(iter, 0) + self._snapStateDic[fsname] = enabled + + def _get_rsync_selection_state(self, model, path, iter): + fsname = self._fsListStore.get_value(iter, 3) + enabled = self._fsListStore.get_value(iter, 1) + self._rsyncStateDic[fsname] = enabled + + def _set_fs_selection_state(self, model, path, iter, selected): + fsname = self._fsListStore.get_value(iter, 3) + self._snapStateDic[fsname] = selected + + def _set_rsync_selection_state(self, model, path, iter, selected): + fsname = self._fsListStore.get_value(iter, 3) + self._rsyncStateDic[fsname] = selected + + def _refine_filesys_actions(self, fsname, inputdic, actions): + selected = inputdic[fsname] + try: + fstag = actions[fsname] + # Found so we can skip over. + except KeyError: + # Need to check parent value to see if + # we should set explicitly or just inherit. + path = fsname.rsplit("/", 1) + parentName = path[0] + if parentName == fsname: + # Means this filesystem is the root of the pool + # so we need to set it explicitly. + actions[fsname] = \ + FilesystemIntention(fsname, selected, False) + else: + parentIntent = None + inherit = False + # Check if parent is already set and if so whether to + # inherit or override with a locally set property value. + try: + # Parent has already been registered + parentIntent = actions[parentName] + except: + # Parent not yet set, so do that recursively to figure + # out if we need to inherit or set a local property on + # this child filesystem. + self._refine_filesys_actions(parentName, + inputdic, + actions) + parentIntent = actions[parentName] + if parentIntent.selected == selected: + inherit = True + actions[fsname] = \ + FilesystemIntention(fsname, selected, inherit) + + def _validate_rsync_target(self, path): + """ + Tests path to see if it is the pre-configured + rsync backup device path. + Returns True on success, otherwise False + """ + # FIXME - this is duplicate in applet.py and rsync-backup.py + # It should be moved into a shared module + if not os.path.exists(path): + return False + testDir = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + testKeyFile = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + if os.path.exists(testDir) and \ + os.path.exists(testKeyFile): + testKeyVal = None + f = open(testKeyFile, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetKey = val.strip() + break + f.close() + if targetKey == self._smfTargetKey: + return True + return False + + + def commit_filesystem_selection(self): + """ + Commits the intended filesystem selection actions based on the + user's UI configuration to disk. Compares with initial startup + configuration and applies the minimum set of necessary changes. + """ + for fsname,fsmountpoint in self._datasets.list_filesystems(): + fs = zfs.Filesystem(fsname, fsmountpoint) + try: + initialIntent = self._initialFsIntentDic[fsname] + intent = self._fsIntentDic[fsname] + if intent == initialIntent: + continue + fs.set_auto_snap(intent.selected, intent.inherited) + + except KeyError: + pass + + def commit_rsync_selection(self): + """ + Commits the intended filesystem selection actions based on the + user's UI configuration to disk. Compares with initial startup + configuration and applies the minimum set of necessary changes. + """ + for fsname,fsmountpoint in self._datasets.list_filesystems(): + fs = zfs.Filesystem(fsname, fsmountpoint) + try: + initialIntent = self._initialRsyncIntentDic[fsname] + intent = self._rsyncIntentDic[fsname] + if intent == initialIntent: + continue + if intent.inherited == True and \ + initialIntent.inherited == False: + fs.unset_user_property(rsyncsmf.RSYNCFSTAG) + else: + if intent.selected == True: + value = "true" + else: + value = "false" + fs.set_user_property(rsyncsmf.RSYNCFSTAG, + value) + except KeyError: + pass + + def setup_rsync_config(self): + if self._rsyncEnabled == True: + if self._newRsyncTargetSelected == True: + sys,nodeName,rel,ver,arch = os.uname() + basePath = os.path.join(self._newRsyncTargetDir, + rsyncsmf.RSYNCDIRPREFIX,) + nodePath = os.path.join(basePath, + nodeName) + configPath = os.path.join(basePath, + rsyncsmf.RSYNCCONFIGFILE) + newKey = generate_random_key() + try: + origmask = os.umask(0222) + if not os.path.exists(nodePath): + os.makedirs(nodePath, 0755) + f = open(configPath, 'w') + f.write("target_key=%s\n" % (newKey)) + f.close() + os.umask(origmask) + except OSError as e: + self._pulseDialog.hide() + sys.stderr.write("Error configuring external " \ + "backup device:\n" \ + "%s\n\nReason:\n %s") \ + % (self._newRsyncTargetDir, str(e)) + sys.exit(-1) + self._rsyncSMF.set_target_dir(self._newRsyncTargetDir) + self._rsyncSMF.set_target_key(newKey) + # Applet monitors rsyncTargetDir so make sure to notify it. + self._configNotify = True + return + + def setup_services(self): + # Take care of the rsync plugin service first since time-slider + # will query it. + # Changes to rsync or time-slider SMF service State should be + # broadcast to let notification applet refresh. + if self._rsyncEnabled == True and \ + self._initialRsyncState == False: + self._rsyncSMF.enable_service() + self._configNotify = True + elif self._rsyncEnabled == False and \ + self._initialRsyncState == True: + self._rsyncSMF.disable_service() + self._configNotify = True + customSelection = self._xml.get_widget("selectfsradio").get_active() + if customSelection != self._initialCustomSelection: + self._sliderSMF.set_custom_selection(customSelection) + if self._initialEnabledState == False: + enable_default_schedules() + self._sliderSMF.enable_service() + self._configNotify = True + + def set_cleanup_level(self): + """ + Wrapper function to set the warning level cleanup threshold + value as a percentage of pool capacity. + """ + level = self._xml.get_widget("capspinbutton").get_value_as_int() + if level != self._initialCleanupLevel: + self._sliderSMF.set_cleanup_level("warning", level) + + def broadcast_changes(self): + """ + Blunt instrument to notify D-Bus listeners such as notification + applet to rescan service configuration + """ + if self._configNotify == False: + return + self._dbus.config_changed() + + def _on_deletesnapshots_clicked(self, widget): + cmdpath = os.path.join(os.path.dirname(self._execPath), \ + "../lib/time-slider-delete") + p = subprocess.Popen(cmdpath, close_fds=True) + + +class EnableService(threading.Thread): + + def __init__(self, setupManager): + threading.Thread.__init__(self) + self._setupManager = setupManager + + def run(self): + try: + # Set the service state last so that the ZFS filesystems + # are correctly tagged before the snapshot scripts check them + self._setupManager.commit_filesystem_selection() + self._setupManager.commit_rsync_selection() + self._setupManager.set_cleanup_level() + self._setupManager.setup_rsync_config() + self._setupManager.setup_services() + self._setupManager.broadcast_changes() + except RuntimeError, message: + sys.stderr.write(str(message)) + +def generate_random_key(length=32): + """ + Returns a 'length' byte character composed of random letters and + unsigned single digit integers. Used to create a random + signature key to identify pre-configured backup directories + for the rsync plugin + """ + from string import letters, digits + from random import choice + return ''.join([choice(letters + digits) \ + for i in range(length)]) + +def main(argv): + rbacp = RBACprofile() + # The setup GUI needs to be run as root in order to ensure + # that the rsync backup target directory is accessible by + # root and to perform validation checks on it. + # This GUI can be launched with an euid of root in one of + # the following 3 ways; + # 0. Run by the superuser (root) + # 1. Run via gksu to allow a non priviliged user to authenticate + # as the superuser (root) + + if os.geteuid() == 0: + manager = SetupManager(argv) + gtk.gdk.threads_enter() + gtk.main() + gtk.gdk.threads_leave() + elif os.path.exists(argv) and os.path.exists("/usr/bin/gksu"): + # Run via gksu, which will prompt for the root password + os.unsetenv("DBUS_SESSION_BUS_ADDRESS") + os.execl("/usr/bin/gksu", "gksu", argv) + # Shouldn't reach this point + sys.exit(1) + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Insufficient Priviliges")) + dialog.format_secondary_text(_("The snapshot manager service requires " + "administrative privileges to run. " + "You have not been assigned the necessary" + "administrative priviliges." + "\n\nConsult your system administrator ")) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + diff --git a/usr/share/time-slider/lib/time_slider/setupgui.py~ b/usr/share/time-slider/lib/time_slider/setupgui.py~ new file mode 100755 index 0000000..b3a37b6 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/setupgui.py~ @@ -0,0 +1,1322 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +import os +import subprocess +import threading +import util +import smf +from autosnapsmf import enable_default_schedules, disable_default_schedules + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import rsyncsmf + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) + +import glib +import gobject +import gio +import dbus +import dbus.service +import dbus.mainloop +import dbus.mainloop.glib +import dbussvc + + +# This is the rough guess ratio used for rsync backup device size +# vs. the total size of the pools it's expected to backup. +RSYNCTARGETRATIO = 2 + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from timeslidersmf import TimeSliderSMF +from rbac import RBACprofile + + +class FilesystemIntention: + + def __init__(self, name, selected, inherited): + self.name = name + self.selected = selected + self.inherited = inherited + + def __str__(self): + return_string = "Filesystem name: " + self.name + \ + "\n\tSelected: " + str(self.selected) + \ + "\n\tInherited: " + str(self.inherited) + return return_string + + def __eq__(self, other): + if self.name != other.name: + return False + if self.inherited and other.inherited: + return True + elif not self.inherited and other.inherited: + return False + if (self.selected == other.selected) and \ + (self.inherited == other.inherited): + return True + else: + return False + +class SetupManager: + + def __init__(self, execpath): + self._execPath = execpath + self._datasets = zfs.Datasets() + self._xml = gtk.glade.XML("%s/../../glade/time-slider-setup.glade" \ + % (os.path.dirname(__file__))) + + # Tell dbus to use the gobject mainloop for async ops + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + dbus.mainloop.glib.threads_init() + + # Register a bus name with the system dbus daemon + systemBus = dbus.SystemBus() + busName = dbus.service.BusName("org.opensolaris.TimeSlider.config", + systemBus) + self._dbus = dbussvc.Config(systemBus, + '/org/opensolaris/TimeSlider/config') + # Used later to trigger a D-Bus notification of select configuration + # changes made + self._configNotify = False + + # These variables record the initial UI state which are used + # later to compare against the UI state when the OK or Cancel + # button is clicked and apply the minimum set of necessary + # configuration changes. Prevents minor changes taking ages + # to be applied by the GUI. + self._initialEnabledState = None + self._initialRsyncState = None + self._initialRsyncTargetDir = None + self._initialCleanupLevel = None + self._initialCustomSelection = False + self._initialSnapStateDic = {} + self._initialRsyncStateDic = {} + self._initialFsIntentDic = {} + self._initialRsyncIntentDic = {} + + # Currently selected rsync backup device via the GUI. + self._newRsyncTargetDir = None + # Used to store GUI filesystem selection state and the + # set of intended properties to apply to zfs filesystems. + self._snapStateDic = {} + self._rsyncStateDic = {} + self._fsIntentDic = {} + self._rsyncIntentDic = {} + # Dictionary that maps device ID numbers to zfs filesystem objects + self._fsDevices = {} + + topLevel = self._xml.get_widget("toplevel") + self._pulseDialog = self._xml.get_widget("pulsedialog") + self._pulseDialog.set_transient_for(topLevel) + + # gio.VolumeMonitor reference + self._vm = gio.volume_monitor_get() + self._vm.connect("mount-added", self._mount_added) + self._vm.connect("mount-removed" , self._mount_removed) + + self._fsListStore = gtk.ListStore(bool, + bool, + str, + str, + gobject.TYPE_PYOBJECT) + filesystems = self._datasets.list_filesystems() + for fsname,fsmountpoint in filesystems: + if (fsmountpoint == "legacy"): + mountpoint = _("Legacy") + else: + mountpoint = fsmountpoint + fs = zfs.Filesystem(fsname, fsmountpoint) + # Note that we don't deal with legacy mountpoints. + if fsmountpoint != "legacy" and fs.is_mounted(): + self._fsDevices[os.stat(fsmountpoint).st_dev] = fs + snap = fs.get_auto_snap() + rsyncstr = fs.get_user_property(rsyncsmf.RSYNCFSTAG) + if rsyncstr == "true": + rsync = True + else: + rsync = False + # Rsync is only performed on snapshotted filesystems. + # So treat as False if rsync is set to true independently + self._fsListStore.append([snap, snap & rsync, + mountpoint, fs.name, fs]) + self._initialSnapStateDic[fs.name] = snap + self._initialRsyncStateDic[fs.name] = snap & rsync + del filesystems + + for fsname in self._initialSnapStateDic: + self._refine_filesys_actions(fsname, + self._initialSnapStateDic, + self._initialFsIntentDic) + self._refine_filesys_actions(fsname, + self._initialRsyncStateDic, + self._initialRsyncIntentDic) + + self._fsTreeView = self._xml.get_widget("fstreeview") + self._fsTreeView.set_sensitive(False) + self._fsTreeView.set_size_request(10, 200) + + self._fsTreeView.set_model(self._fsListStore) + + cell0 = gtk.CellRendererToggle() + cell1 = gtk.CellRendererToggle() + cell2 = gtk.CellRendererText() + cell3 = gtk.CellRendererText() + + radioColumn = gtk.TreeViewColumn(_("Select"), + cell0, active=0) + self._fsTreeView.insert_column(radioColumn, 0) + + self._rsyncRadioColumn = gtk.TreeViewColumn(_("Replicate"), + cell1, active=1) + nameColumn = gtk.TreeViewColumn(_("Mount Point"), + cell2, text=2) + self._fsTreeView.insert_column(nameColumn, 2) + mountPointColumn = gtk.TreeViewColumn(_("File System Name"), + cell3, text=3) + self._fsTreeView.insert_column(mountPointColumn, 3) + cell0.connect('toggled', self._row_toggled) + cell1.connect('toggled', self._rsync_cell_toggled) + advancedBox = self._xml.get_widget("advancedbox") + advancedBox.connect('unmap', self._advancedbox_unmap) + + self._rsyncSMF = rsyncsmf.RsyncSMF("%s:rsync" \ + %(plugin.PLUGINBASEFMRI)) + state = self._rsyncSMF.get_service_state() + self._initialRsyncTargetDir = self._rsyncSMF.get_target_dir() + # Check for the default, unset value of "" from SMF. + if self._initialRsyncTargetDir == '""': + self._initialRsyncTargetDir = '' + self._newRsyncTargetDir = self._initialRsyncTargetDir + self._smfTargetKey = self._rsyncSMF.get_target_key() + self._newRsyncTargetSelected = False + sys,self._nodeName,rel,ver,arch = os.uname() + + # Model columns: + # 0 Themed icon list (python list) + # 1 device root + # 2 volume name + # 3 Is gio.Mount device + # 4 Is separator (for comboBox separator rendering) + self._rsyncStore = gtk.ListStore(gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN) + self._rsyncCombo = self._xml.get_widget("rsyncdevcombo") + mounts = self._vm.get_mounts() + for mount in mounts: + self._mount_added(self._vm, mount) + if len(mounts) > 0: + # Add a separator + self._rsyncStore.append((None, None, None, None, True)) + del mounts + + if len(self._newRsyncTargetDir) == 0: + self._rsyncStore.append((['folder'], + _("(None)"), + '', + False, + False)) + # Add a separator + self._rsyncStore.append((None, None, None, None, True)) + self._rsyncStore.append((None, _("Other..."), "Other", False, False)) + self._iconCell = gtk.CellRendererPixbuf() + self._nameCell = gtk.CellRendererText() + self._rsyncCombo.clear() + self._rsyncCombo.pack_start(self._iconCell, False) + self._rsyncCombo.set_cell_data_func(self._iconCell, + self._icon_cell_render) + self._rsyncCombo.pack_end(self._nameCell) + self._rsyncCombo.set_attributes(self._nameCell, text=1) + self._rsyncCombo.set_row_separator_func(self._row_separator) + self._rsyncCombo.set_model(self._rsyncStore) + self._rsyncCombo.connect("changed", self._rsync_combo_changed) + # Force selection of currently configured device + self._rsync_dev_selected(self._newRsyncTargetDir) + + # signal dictionary + dic = {"on_ok_clicked" : self._on_ok_clicked, + "on_cancel_clicked" : gtk.main_quit, + "on_snapshotmanager_delete_event" : gtk.main_quit, + "on_enablebutton_toggled" : self._on_enablebutton_toggled, + "on_rsyncbutton_toggled" : self._on_rsyncbutton_toggled, + "on_defaultfsradio_toggled" : self._on_defaultfsradio_toggled, + "on_selectfsradio_toggled" : self._on_selectfsradio_toggled, + "on_deletesnapshots_clicked" : self._on_deletesnapshots_clicked} + self._xml.signal_autoconnect(dic) + + if state != "disabled": + self._rsyncEnabled = True + self._xml.get_widget("rsyncbutton").set_active(True) + self._initialRsyncState = True + else: + self._rsyncEnabled = False + self._rsyncCombo.set_sensitive(False) + self._initialRsyncState = False + + # Initialise SMF service instance state. + try: + self._sliderSMF = TimeSliderSMF() + except RuntimeError,message: + self._xml.get_widget("toplevel").set_sensitive(False) + dialog = gtk.MessageDialog(self._xml.get_widget("toplevel"), + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot manager service error")) + dialog.format_secondary_text(_("The snapshot manager service does " + "not appear to be installed on this " + "system." + "\n\nSee the svcs(1) man page for more " + "information." + "\n\nDetails:\n%s")%(message)) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + + if self._sliderSMF.svcstate == "disabled": + self._xml.get_widget("enablebutton").set_active(False) + self._initialEnabledState = False + elif self._sliderSMF.svcstate == "offline": + self._xml.get_widget("toplevel").set_sensitive(False) + errors = ''.join("%s\n" % (error) for error in \ + self._sliderSMF.find_dependency_errors()) + dialog = gtk.MessageDialog(self._xml.get_widget("toplevel"), + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot manager service dependency error")) + dialog.format_secondary_text(_("The snapshot manager service has " + "been placed offline due to a dependency " + "problem. The following dependency problems " + "were found:\n\n%s\n\nRun \"svcs -xv\" from " + "a command prompt for more information about " + "these dependency problems.") % errors) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + elif self._sliderSMF.svcstate == "maintenance": + self._xml.get_widget("toplevel").set_sensitive(False) + dialog = gtk.MessageDialog(self._xml.get_widget("toplevel"), + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Snapshot manager service error")) + dialog.format_secondary_text(_("The snapshot manager service has " + "encountered a problem and has been " + "disabled until the problem is fixed." + "\n\nSee the svcs(1) man page for more " + "information.")) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + else: + # FIXME: Check transitional states + self._xml.get_widget("enablebutton").set_active(True) + self._initialEnabledState = True + + + # Emit a toggled signal so that the initial GUI state is consistent + self._xml.get_widget("enablebutton").emit("toggled") + # Check the snapshotting policy (UserData (default), or Custom) + self._initialCustomSelection = self._sliderSMF.is_custom_selection() + if self._initialCustomSelection == True: + self._xml.get_widget("selectfsradio").set_active(True) + # Show the advanced controls so the user can see the + # customised configuration. + if self._sliderSMF.svcstate != "disabled": + self._xml.get_widget("expander").set_expanded(True) + else: # "false" or any other non "true" value + self._xml.get_widget("defaultfsradio").set_active(True) + + # Set the cleanup threshhold value + spinButton = self._xml.get_widget("capspinbutton") + critLevel = self._sliderSMF.get_cleanup_level("critical") + warnLevel = self._sliderSMF.get_cleanup_level("warning") + + # Force the warning level to something practical + # on the lower end, and make it no greater than the + # critical level specified in the SVC instance. + spinButton.set_range(70, critLevel) + self._initialCleanupLevel = warnLevel + if warnLevel > 70: + spinButton.set_value(warnLevel) + else: + spinButton.set_value(70) + + def _icon_cell_render(self, celllayout, cell, model, iter): + iconList = self._rsyncStore.get_value(iter, 0) + if iconList != None: + gicon = gio.ThemedIcon(iconList) + cell.set_property("gicon", gicon) + else: + root = self._rsyncStore.get_value(iter, 2) + if root == "Other": + cell.set_property("gicon", None) + + def _row_separator(self, model, iter): + return model.get_value(iter, 4) + + def _mount_added(self, volume_monitor, mount): + icon = mount.get_icon() + iconList = icon.get_names() + if iconList == None: + iconList = ['drive-harddisk', 'drive'] + root = mount.get_root() + path = root.get_path() + mountName = mount.get_name() + volume = mount.get_volume() + if volume == None: + volName = mount.get_name() + if volName == None: + volName = os.path.split(path)[1] + else: + volName = volume.get_name() + + # Check to see if there is at least one gio.Mount device already + # in the ListStore. If not, then we also need to add a separator + # row. + iter = self._rsyncStore.get_iter_first() + if iter and self._rsyncStore.get_value(iter, 3) == False: + self._rsyncStore.insert(0, (None, None, None, None, True)) + + self._rsyncStore.insert(0, (iconList, volName, path, True, False)) + # If this happens to be the already configured backup device + # and the user hasn't tried to change device yet, auto select + # it. + if self._initialRsyncTargetDir == self._newRsyncTargetDir: + if self._validate_rsync_target(path) == True: + self._rsyncCombo.set_active(0) + + def _mount_removed(self, volume_monitor, mount): + root = mount.get_root() + path = root.get_path() + iter = self._rsyncStore.get_iter_first() + mountIter = None + numMounts = 0 + # Search gio.Mount devices + while iter != None and \ + self._rsyncStore.get_value(iter, 3) == True: + numMounts += 1 + compPath = self._rsyncStore.get_value(iter, 2) + if compPath == path: + mountIter = iter + break + else: + iter = self._rsyncStore.iter_next(iter) + if mountIter != None: + if numMounts == 1: + # Need to remove the separator also since + # there will be no more gio.Mount devices + # shown in the combo box + sepIter = self._rsyncStore.iter_next(mountIter) + if self._rsyncStore.get_value(sepIter, 4) == True: + self._rsyncStore.remove(sepIter) + self._rsyncStore.remove(mountIter) + iter = self._rsyncStore.get_iter_first() + # Insert a custom folder if none exists already + if self._rsyncStore.get_value(iter, 2) == "Other": + path = self._initialRsyncTargetDir + length = len(path) + if length > 1: + name = os.path.split(path)[1] + elif length == 1: + name = path + else: # Indicates path is unset: '' + name = _("(None)") + iter = self._rsyncStore.insert_before(iter, + (None, + None, + None, + None, + True)) + iter = self._rsyncStore.insert_before(iter, + (['folder'], + name, + path, + False, + False)) + self._rsyncCombo.set_active_iter(iter) + + def _monitor_setup(self, pulseBar): + if self._enabler.isAlive() == True: + pulseBar.pulse() + return True + else: + gtk.main_quit() + + def _row_toggled(self, renderer, path): + model = self._fsTreeView.get_model() + iter = model.get_iter(path) + state = renderer.get_active() + if state == False: + self._fsListStore.set_value(iter, 0, True) + else: + self._fsListStore.set_value(iter, 0, False) + self._fsListStore.set_value(iter, 1, False) + + def _rsync_cell_toggled(self, renderer, path): + model = self._fsTreeView.get_model() + iter = model.get_iter(path) + state = renderer.get_active() + rowstate = self._fsListStore.get_value(iter, 0) + if rowstate == True: + if state == False: + self._fsListStore.set_value(iter, 1, True) + else: + self._fsListStore.set_value(iter, 1, False) + + def _rsync_config_error(self, msg): + topLevel = self._xml.get_widget("toplevel") + dialog = gtk.MessageDialog(topLevel, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Unsuitable Backup Location")) + dialog.format_secondary_text(msg) + dialog.set_icon_name("time-slider-setup") + dialog.run() + dialog.hide() + return + + def _rsync_dev_selected(self, path): + iter = self._rsyncStore.get_iter_first() + while iter != None: + # Break out when we hit a non gio.Mount device + if self._rsyncStore.get_value(iter, 3) == False: + break + compPath = self._rsyncStore.get_value(iter, 2) + if compPath == path: + self._rsyncCombo.set_active_iter(iter) + self._newRsyncTargetDir = path + return + else: + iter = self._rsyncStore.iter_next(iter) + + # Not one of the shortcut RMM devices, so it's + # some other path on the filesystem. + # iter may be pointing at a separator. Increment + # to next row iter if so. + if self._rsyncStore.get_value(iter, 4) == True: + iter = self._rsyncStore.iter_next(iter) + + if iter != None: + if len(path) > 1: + name = os.path.split(path)[1] + elif len(path) == 1: + name = path + else: # Indicates path is unset: '' + name = _("(None)") + # Could be either the custom folder selection + # row or the "Other" row if the custom row + # was not created. If "Other" then create the + # custom row and separator now at this position + if self._rsyncStore.get_value(iter, 2) == "Other": + iter = self._rsyncStore.insert_before(iter, + (None, + None, + None, + None, + True)) + iter = self._rsyncStore.insert_before(iter, + (['folder'], + name, + path, + False, + False)) + else: + self._rsyncStore.set(iter, + 1, name, + 2, path) + self._rsyncCombo.set_active_iter(iter) + self._newRsyncTargetDir = path + + def _rsync_combo_changed(self, combobox): + newIter = combobox.get_active_iter() + if newIter != None: + root = self._rsyncStore.get_value(newIter, 2) + if root != "Other": + self._newRsyncTargetDir = root + else: + msg = _("Select A Back Up Device") + fileDialog = \ + gtk.FileChooserDialog( + msg, + self._xml.get_widget("toplevel"), + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL, + gtk.STOCK_OK,gtk.RESPONSE_OK), + None) + self._rsyncCombo.set_sensitive(False) + response = fileDialog.run() + fileDialog.hide() + if response == gtk.RESPONSE_OK: + gFile = fileDialog.get_file() + self._rsync_dev_selected(gFile.get_path()) + else: + self._rsync_dev_selected(self._newRsyncTargetDir) + self._rsyncCombo.set_sensitive(True) + + def _rsync_size_warning(self, zpools, zpoolSize, + rsyncTarget, targetSize): + # Using decimal "GB" instead of binary "GiB" + KB = 1000 + MB = 1000 * KB + GB = 1000 * MB + TB = 1000 * GB + + suggestedSize = RSYNCTARGETRATIO * zpoolSize + if suggestedSize > TB: + sizeStr = "%.1f TB" % round(suggestedSize / float(TB), 1) + elif suggestedSize > GB: + sizeStr = "%.1f GB" % round(suggestedSize / float(GB), 1) + else: + sizeStr = "%.1f MB" % round(suggestedSize / float(MB), 1) + + if targetSize > TB: + targetStr = "%.1f TB" % round(targetSize / float(TB), 1) + elif targetSize > GB: + targetStr = "%.1f GB" % round(targetSize / float(GB), 1) + else: + targetStr = "%.1f MB" % round(targetSize / float(MB), 1) + + + msg = _("Time Slider suggests a device with a capacity of at " + "least %s.\n" + "The device: \'%s\'\nonly has %s\n" + "Do you want to use it anyway?") \ + % (sizeStr, rsyncTarget, targetStr) + + topLevel = self._xml.get_widget("toplevel") + dialog = gtk.MessageDialog(topLevel, + 0, + gtk.MESSAGE_QUESTION, + gtk.BUTTONS_YES_NO, + _("Time Slider")) + dialog.set_default_response(gtk.RESPONSE_NO) + dialog.set_transient_for(topLevel) + dialog.set_markup(msg) + dialog.set_icon_name("time-slider-setup") + + response = dialog.run() + dialog.hide() + if response == gtk.RESPONSE_YES: + return True + else: + return False + + def _check_rsync_config(self): + """ + Checks rsync configuration including, filesystem selection, + target directory validation and capacity checks. + Returns True if everything is OK, otherwise False. + Pops up blocking error dialogs to notify users of error + conditions before returning. + """ + def _get_mount_point(path): + if os.path.ismount(path): + return path + else: + return _get_mount_point(abspath(join(path, pardir))) + + if self._rsyncEnabled != True: + return True + + if len(self._newRsyncTargetDir) == 0: + msg = _("No backup device was selected.\n" + "Please select an empty device.") + self._rsync_config_error(msg) + return False + # There's little that can be done if the device is from a + # previous configuration and currently offline. So just + # treat it as being OK based on the assumption that it was + # previously deemed to be OK. + if self._initialRsyncTargetDir == self._newRsyncTargetDir and \ + not os.path.exists(self._newRsyncTargetDir): + return True + # Perform the required validation checks on the + # target directory. + newTargetDir = self._newRsyncTargetDir + + # We require the whole device. So find the enclosing + # mount point and inspect from there. + targetMountPoint = abspath(_get_mount_point(newTargetDir)) + + # Check that it's writable. + f = None + testFile = os.path.join(targetMountPoint, ".ts-test") + try: + f = open(testFile, 'w') + except (OSError, IOError): + msg = _("\'%s\'\n" + "is not writable. The backup device must " + "be writable by the system administrator." + "\n\nPlease use a different device.") \ + % (targetMountPoint) + self._rsync_config_error(msg) + return False + f.close() + + # Try to create a symlink. Rsync requires this to + # do incremental backups and to ensure it's posix like + # enough to correctly set file ownerships and perms. + os.chdir(targetMountPoint) + try: + os.link(testFile, ".ts-test-link") + except OSError: + msg = _("\'%s\'\n" + "contains an incompatible file system. " + "The selected device must have a Unix " + "style file system that supports file " + "linking, such as UFS" + "\n\nPlease use a different device.") \ + % (targetMountPoint) + self._rsync_config_error(msg) + return False + finally: + os.unlink(testFile) + os.unlink(".ts-test-link") + + # Check that selected directory is either empty + # or already preconfigured as a backup target + sys,nodeName,rel,ver,arch = os.uname() + basePath = os.path.join(targetMountPoint, + rsyncsmf.RSYNCDIRPREFIX) + nodePath = os.path.join(basePath, + nodeName) + configPath = os.path.join(basePath, + rsyncsmf.RSYNCCONFIGFILE) + self._newRsyncTargetSelected = True + targetDirKey = None + + contents = os.listdir(targetMountPoint) + os.chdir(targetMountPoint) + + # The only other exception to an empty directory is + # "lost+found". + for item in contents: + if (item != rsyncsmf.RSYNCDIRPREFIX and \ + item != "lost+found") or \ + not os.path.isdir(item) or \ + os.path.islink(item): + msg = _("\'%s\'\n is not an empty device.\n\n" + "Please select an empty device.") \ + % (newTargetDir) + self._rsync_config_error(msg) + return False + + # Validate existing directory structure + if os.path.exists(basePath): + # We only accept a pre-existing directory if + # 1. It has a config key that matches that stored by + # the rsync plugin's SMF configuration + # 2. It has a single subfolder that matches the nodename + # of this system, + + # Check for previous config key + if os.path.exists(configPath): + f = open(configPath, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetDirKey = val.strip() + break + + # Examine anything else in the directory + self._targetSelectionError = None + dirList = [d for d in os.listdir(basePath) if + d != '.rsync-config'] + os.chdir(basePath) + if len(dirList) > 0: + msg = _("\'%s\'\n is not an empty device.\n\n" + "Please select an empty device.") \ + % (newTargetDir) + # No config key or > 1 directory: + # User specified a non empty directory. + if targetDirKey == None or len(dirList) > 1: + self._rsync_config_error(msg) + return False + # Make sure the single item is not a file or symlink. + elif os.path.islink(dirList[0]) or \ + os.path.isfile(dirList[0]): + self._rsync_config_error(msg) + return False + else: + # Has 1 other item and a config key. Other + # item must be a directory and must match the + # system nodename and SMF's key value respectively + # respectively + if dirList[0] != nodeName and \ + targetDirKey != self._smfTargetKey: + msg = _("\'%s\'\n" + "is a Time Slider external backup device " + "that is already in use by another system. " + "Backup devices may not be shared between " + "systems." + "\n\nPlease use a different device.") \ + % (newTargetDir) + self._rsync_config_error(msg) + return False + else: + if dirList[0] == nodeName and \ + targetDirKey != self._smfTargetKey: + # Looks like a device that we previously used, + # but discontinued using in favour of some other + # device. + msg = _("\'%s\' appears to be a a device " + "previously configured for use by this " + "system.\n\nDo you want resume use of " + "this device for backups?") \ + % (newTargetDir) + + topLevel = self._xml.get_widget("toplevel") + dialog = gtk.MessageDialog(topLevel, + 0, + gtk.MESSAGE_QUESTION, + gtk.BUTTONS_YES_NO, + _("Time Slider")) + dialog.set_default_response(gtk.RESPONSE_NO) + dialog.set_transient_for(topLevel) + dialog.set_markup(msg) + dialog.set_icon_name("time-slider-setup") + + response = dialog.run() + dialog.hide() + if response == gtk.RESPONSE_NO: + return False + else: + # Appears to be our own pre-configured directory. + self._newRsyncTargetSelected = False + + # Compare device ID against selected ZFS filesystems + # and their enclosing Zpools. The aim is to avoid + # a vicous circle caused by backing up snapshots onto + # the same pool the snapshots originate from + targetDev = os.stat(newTargetDir).st_dev + try: + fs = self._fsDevices[targetDev] + + # See if the filesystem itself is selected + # and/or any other fileystem on the pool is + # selected. + fsEnabled = self._snapStateDic[fs.name] + if fsEnabled == True: + # Definitely can't use this since it's a + # snapshotted filesystem. + msg = _("\'%s\'\n" + "belongs to the ZFS filesystem \'%s\' " + "which is already selected for " + "regular ZFS snaphots." + "\n\nPlease select a drive " + "not already in use by " + "Time Slider") \ + % (newTargetDir, fs.name) + self._rsync_config_error(msg) + return False + else: + # See if there is anything else on the pool being + # snapshotted + poolName = fs.name.split("/", 1)[0] + for name,mount in self._datasets.list_filesystems(): + if name.find(poolName) == 0: + try: + otherEnabled = self._snapStateDic[name] + radioBtn = self._xml.get_widget("defaultfsradio") + snapAll = radioBtn.get_active() + if snapAll or otherEnabled: + msg = _("\'%s\'\n" + "belongs to the ZFS pool \'%s\' " + "which is already being used " + "to store ZFS snaphots." + "\n\nPlease select a drive " + "not already in use by " + "Time Slider") \ + % (newTargetDir, poolName) + self._rsync_config_error(msg) + return False + except KeyError: + pass + except KeyError: + # No match found - good. + pass + + + # Figure out if there's a reasonable amount of free space to + # store backups. This is a vague guess at best. + allPools = zfs.list_zpools() + snapPools = [] + # FIXME - this is for custom selection. There is a short + # circuit case for default (All) configuration. Don't forget + # to implement this short circuit. + for poolName in allPools: + try: + snapPools.index(poolName) + except ValueError: + pool = zfs.ZPool(poolName) + # FIXME - we should include volumes here but they + # can only be set from the command line, not via + # the GUI, so not crucial. + for fsName,mount in pool.list_filesystems(): + # Don't try to catch exception. The filesystems + # are already populated in self._snapStateDic + enabled = self._snapStateDic[fsName] + if enabled == True: + snapPools.append(poolName) + break + + sumPoolSize = 0 + for poolName in snapPools: + pool = zfs.ZPool(poolName) + # Rough calcualation, but precise enough for + # estimation purposes + sumPoolSize += pool.get_used_size() + sumPoolSize += pool.get_available_size() + + + # Compare with available space on rsync target dir + targetAvail = util.get_available_size(targetMountPoint) + targetUsed = util.get_used_size(targetMountPoint) + targetSum = targetAvail + targetUsed + + # Recommended Minimum: + # At least double the combined size of all pools with + # fileystems selected for backup. Variables include, + # frequency of data changes, how much efficiency rsync + # sacrifices compared to ZFS' block level diff tracking, + # whether compression and/or deduplication are enabled + # on the source pool/fileystem. + # We don't try to make calculations based on individual + # filesystem selection as there are too many unpredictable + # variables to make an estimation of any practical use. + # Let the user figure that out for themselves. + + # The most consistent measurement is to use the sum of + # available and used size on the target fileystem. We + # assume based on previous checks that the target device + # is only being used for rsync backups and therefore the + # used value consists of existing backups and is. Available + # space can be reduced for various reasons including the used + # value increasing or for nfs mounted zfs fileystems, other + # zfs filesystems on the containing pool using up more space. + + + targetPoolRatio = targetSum/float(sumPoolSize) + if (targetPoolRatio < RSYNCTARGETRATIO): + response = self._rsync_size_warning(snapPools, + sumPoolSize, + targetMountPoint, + targetSum) + if response == False: + return False + + self._newRsyncTargetDir = targetMountPoint + return True + + def _on_ok_clicked(self, widget): + # Make sure the dictionaries are empty. + self._fsIntentDic = {} + self._snapStateDic = {} + self._rsyncStateDic = {} + enabled = self._xml.get_widget("enablebutton").get_active() + self._rsyncEnabled = self._xml.get_widget("rsyncbutton").get_active() + if enabled == False: + if self._rsyncEnabled == False and \ + self._initialRsyncState == True: + self._rsyncSMF.disable_service() + if self._initialEnabledState == True: + self._sliderSMF.disable_service() + # Ignore other changes to the snapshot/rsync configuration + # of filesystems. Just broadcast the change and exit. + self._configNotify = True + self.broadcast_changes() + gtk.main_quit() + else: + model = self._fsTreeView.get_model() + snapalldata = self._xml.get_widget("defaultfsradio").get_active() + + if snapalldata == True: + model.foreach(self._set_fs_selection_state, True) + if self._rsyncEnabled == True: + model.foreach(self._set_rsync_selection_state, True) + else: + model.foreach(self._get_fs_selection_state) + model.foreach(self._get_rsync_selection_state) + for fsname in self._snapStateDic: + self._refine_filesys_actions(fsname, + self._snapStateDic, + self._fsIntentDic) + if self._rsyncEnabled == True: + self._refine_filesys_actions(fsname, + self._rsyncStateDic, + self._rsyncIntentDic) + if self._rsyncEnabled and \ + not self._check_rsync_config(): + return + + self._pulseDialog.show() + self._enabler = EnableService(self) + self._enabler.start() + glib.timeout_add(100, + self._monitor_setup, + self._xml.get_widget("pulsebar")) + + def _on_enablebutton_toggled(self, widget): + expander = self._xml.get_widget("expander") + enabled = widget.get_active() + self._xml.get_widget("filesysframe").set_sensitive(enabled) + expander.set_sensitive(enabled) + if (enabled == False): + expander.set_expanded(False) + + def _on_rsyncbutton_toggled(self, widget): + self._rsyncEnabled = widget.get_active() + if self._rsyncEnabled == True: + self._fsTreeView.insert_column(self._rsyncRadioColumn, 1) + self._rsyncCombo.set_sensitive(True) + else: + self._fsTreeView.remove_column(self._rsyncRadioColumn) + self._rsyncCombo.set_sensitive(False) + + def _on_defaultfsradio_toggled(self, widget): + if widget.get_active() == True: + self._xml.get_widget("fstreeview").set_sensitive(False) + + def _on_selectfsradio_toggled(self, widget): + if widget.get_active() == True: + self._xml.get_widget("fstreeview").set_sensitive(True) + + def _advancedbox_unmap(self, widget): + # Auto shrink the window by subtracting the frame's height + # requistion from the window's height requisition + myrequest = widget.size_request() + toplevel = self._xml.get_widget("toplevel") + toprequest = toplevel.size_request() + toplevel.resize(toprequest[0], toprequest[1] - myrequest[1]) + + def _get_fs_selection_state(self, model, path, iter): + fsname = self._fsListStore.get_value(iter, 3) + enabled = self._fsListStore.get_value(iter, 0) + self._snapStateDic[fsname] = enabled + + def _get_rsync_selection_state(self, model, path, iter): + fsname = self._fsListStore.get_value(iter, 3) + enabled = self._fsListStore.get_value(iter, 1) + self._rsyncStateDic[fsname] = enabled + + def _set_fs_selection_state(self, model, path, iter, selected): + fsname = self._fsListStore.get_value(iter, 3) + self._snapStateDic[fsname] = selected + + def _set_rsync_selection_state(self, model, path, iter, selected): + fsname = self._fsListStore.get_value(iter, 3) + self._rsyncStateDic[fsname] = selected + + def _refine_filesys_actions(self, fsname, inputdic, actions): + selected = inputdic[fsname] + try: + fstag = actions[fsname] + # Found so we can skip over. + except KeyError: + # Need to check parent value to see if + # we should set explicitly or just inherit. + path = fsname.rsplit("/", 1) + parentName = path[0] + if parentName == fsname: + # Means this filesystem is the root of the pool + # so we need to set it explicitly. + actions[fsname] = \ + FilesystemIntention(fsname, selected, False) + else: + parentIntent = None + inherit = False + # Check if parent is already set and if so whether to + # inherit or override with a locally set property value. + try: + # Parent has already been registered + parentIntent = actions[parentName] + except: + # Parent not yet set, so do that recursively to figure + # out if we need to inherit or set a local property on + # this child filesystem. + self._refine_filesys_actions(parentName, + inputdic, + actions) + parentIntent = actions[parentName] + if parentIntent.selected == selected: + inherit = True + actions[fsname] = \ + FilesystemIntention(fsname, selected, inherit) + + def _validate_rsync_target(self, path): + """ + Tests path to see if it is the pre-configured + rsync backup device path. + Returns True on success, otherwise False + """ + # FIXME - this is duplicate in applet.py and rsync-backup.py + # It should be moved into a shared module + if not os.path.exists(path): + return False + testDir = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + self._nodeName) + testKeyFile = os.path.join(path, + rsyncsmf.RSYNCDIRPREFIX, + rsyncsmf.RSYNCCONFIGFILE) + if os.path.exists(testDir) and \ + os.path.exists(testKeyFile): + testKeyVal = None + f = open(testKeyFile, 'r') + for line in f.readlines(): + key, val = line.strip().split('=') + if key.strip() == "target_key": + targetKey = val.strip() + break + f.close() + if targetKey == self._smfTargetKey: + return True + return False + + + def commit_filesystem_selection(self): + """ + Commits the intended filesystem selection actions based on the + user's UI configuration to disk. Compares with initial startup + configuration and applies the minimum set of necessary changes. + """ + for fsname,fsmountpoint in self._datasets.list_filesystems(): + fs = zfs.Filesystem(fsname, fsmountpoint) + try: + initialIntent = self._initialFsIntentDic[fsname] + intent = self._fsIntentDic[fsname] + if intent == initialIntent: + continue + fs.set_auto_snap(intent.selected, intent.inherited) + + except KeyError: + pass + + def commit_rsync_selection(self): + """ + Commits the intended filesystem selection actions based on the + user's UI configuration to disk. Compares with initial startup + configuration and applies the minimum set of necessary changes. + """ + for fsname,fsmountpoint in self._datasets.list_filesystems(): + fs = zfs.Filesystem(fsname, fsmountpoint) + try: + initialIntent = self._initialRsyncIntentDic[fsname] + intent = self._rsyncIntentDic[fsname] + if intent == initialIntent: + continue + if intent.inherited == True and \ + initialIntent.inherited == False: + fs.unset_user_property(rsyncsmf.RSYNCFSTAG) + else: + if intent.selected == True: + value = "true" + else: + value = "false" + fs.set_user_property(rsyncsmf.RSYNCFSTAG, + value) + except KeyError: + pass + + def setup_rsync_config(self): + if self._rsyncEnabled == True: + if self._newRsyncTargetSelected == True: + sys,nodeName,rel,ver,arch = os.uname() + basePath = os.path.join(self._newRsyncTargetDir, + rsyncsmf.RSYNCDIRPREFIX,) + nodePath = os.path.join(basePath, + nodeName) + configPath = os.path.join(basePath, + rsyncsmf.RSYNCCONFIGFILE) + newKey = generate_random_key() + try: + origmask = os.umask(0222) + if not os.path.exists(nodePath): + os.makedirs(nodePath, 0755) + f = open(configPath, 'w') + f.write("target_key=%s\n" % (newKey)) + f.close() + os.umask(origmask) + except OSError as e: + self._pulseDialog.hide() + sys.stderr.write("Error configuring external " \ + "backup device:\n" \ + "%s\n\nReason:\n %s") \ + % (self._newRsyncTargetDir, str(e)) + sys.exit(-1) + self._rsyncSMF.set_target_dir(self._newRsyncTargetDir) + self._rsyncSMF.set_target_key(newKey) + # Applet monitors rsyncTargetDir so make sure to notify it. + self._configNotify = True + return + + def setup_services(self): + # Take care of the rsync plugin service first since time-slider + # will query it. + # Changes to rsync or time-slider SMF service State should be + # broadcast to let notification applet refresh. + if self._rsyncEnabled == True and \ + self._initialRsyncState == False: + self._rsyncSMF.enable_service() + self._configNotify = True + elif self._rsyncEnabled == False and \ + self._initialRsyncState == True: + self._rsyncSMF.disable_service() + self._configNotify = True + customSelection = self._xml.get_widget("selectfsradio").get_active() + if customSelection != self._initialCustomSelection: + self._sliderSMF.set_custom_selection(customSelection) + if self._initialEnabledState == False: + enable_default_schedules() + self._sliderSMF.enable_service() + self._configNotify = True + + def set_cleanup_level(self): + """ + Wrapper function to set the warning level cleanup threshold + value as a percentage of pool capacity. + """ + level = self._xml.get_widget("capspinbutton").get_value_as_int() + if level != self._initialCleanupLevel: + self._sliderSMF.set_cleanup_level("warning", level) + + def broadcast_changes(self): + """ + Blunt instrument to notify D-Bus listeners such as notification + applet to rescan service configuration + """ + if self._configNotify == False: + return + self._dbus.config_changed() + + def _on_deletesnapshots_clicked(self, widget): + cmdpath = os.path.join(os.path.dirname(self._execPath), \ + "../lib/time-slider-delete") + p = subprocess.Popen(cmdpath, close_fds=True) + + +class EnableService(threading.Thread): + + def __init__(self, setupManager): + threading.Thread.__init__(self) + self._setupManager = setupManager + + def run(self): + try: + # Set the service state last so that the ZFS filesystems + # are correctly tagged before the snapshot scripts check them + self._setupManager.commit_filesystem_selection() + self._setupManager.commit_rsync_selection() + self._setupManager.set_cleanup_level() + self._setupManager.setup_rsync_config() + self._setupManager.setup_services() + self._setupManager.broadcast_changes() + except RuntimeError, message: + sys.stderr.write(str(message)) + +def generate_random_key(length=32): + """ + Returns a 'length' byte character composed of random letters and + unsigned single digit integers. Used to create a random + signature key to identify pre-configured backup directories + for the rsync plugin + """ + from string import letters, digits + from random import choice + return ''.join([choice(letters + digits) \ + for i in range(length)]) + +def main(argv): + rbacp = RBACprofile() + # The setup GUI needs to be run as root in order to ensure + # that the rsync backup target directory is accessible by + # root and to perform validation checks on it. + # This GUI can be launched with an euid of root in one of + # the following 3 ways; + # 0. Run by the superuser (root) + # 1. Run via gksu to allow a non priviliged user to authenticate + # as the superuser (root) + + if os.geteuid() == 0: + manager = SetupManager(argv) + gtk.gdk.threads_enter() + gtk.main() + gtk.gdk.threads_leave() + elif os.path.exists(argv) and os.path.exists("/usr/bin/gksu"): + # Run via gksu, which will prompt for the root password + os.unsetenv("DBUS_SESSION_BUS_ADDRESS") + os.execl("/usr/bin/gksu", "gksu", argv) + # Shouldn't reach this point + sys.exit(1) + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Insufficient Priviliges")) + dialog.format_secondary_text(_("The snapshot manager service requires " + "administrative privileges to run. " + "You have not been assigned the necessary" + "administrative priviliges." + "\n\nConsult your system administrator ")) + dialog.set_icon_name("time-slider-setup") + dialog.run() + sys.exit(1) + diff --git a/usr/share/time-slider/lib/time_slider/smf.py b/usr/share/time-slider/lib/time_slider/smf.py new file mode 100644 index 0000000..b241592 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/smf.py @@ -0,0 +1,153 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import subprocess +import threading +import util + +#SMF EXIT CODES +SMF_EXIT_OK = 0 +SMF_EXIT_ERR_FATAL = 95 +SMF_EXIT_ERR_CONFIG = 96 +SMF_EXIT_MON_DEGRADE = 97 +SMF_EXIT_MON_OFFLINE = 98 +SMF_EXIT_ERR_NOSMF = 99 +SMF_EXIT_ERR_PERM = 100 +#SMF_EXIT_ERR_OTHER = non-zero + + +# Commonly used command paths +PFCMD = "/usr/bin/pfexec" +SVCSCMD = "/usr/bin/svcs" +SVCADMCMD = "/usr/sbin/svcadm" +SVCCFGCMD = "/usr/sbin/svccfg" +SVCPROPCMD = "/usr/bin/svcprop" + + +class SMFInstance(Exception): + + def __init__(self, instanceName): + self.instanceName = instanceName + self.svcstate = self.get_service_state() + self.svcdeps = self.get_service_dependencies() + + + def get_service_dependencies(self): + cmd = [SVCSCMD, "-H", "-o", "fmri", "-d", self.instanceName] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip().split("\n") + return result + + def get_verbose(self): + cmd = [SVCPROPCMD, "-c", "-p", \ + DAEMONPROPGROUP + '/' + "verbose", \ + self.instanceName] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + if result == "true": + return True + else: + return False + + def find_dependency_errors(self): + errors = [] + #FIXME - do this in one pass. + for dep in self.svcdeps: + cmd = [SVCSCMD, "-H", "-o", "state", dep] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + if result != "online": + errors.append("%s\t%s" % (result, dep)) + return errors + + def get_service_state(self): + cmd = [SVCSCMD, "-H", "-o", "state", self.instanceName] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + return result + + def get_prop(self, propgroup, propname): + cmd = [SVCPROPCMD, "-c", "-p", \ + propgroup + '/' + propname,\ + self.instanceName] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + + return result + + def set_prop(self, propgroup, propname, proptype, value): + cmd = [PFCMD, SVCCFGCMD, "-s", self.instanceName, "setprop", \ + propgroup + '/' + propname, "=", proptype + ":", \ + value] + util.run_command(cmd) + self.refresh_service() + + def set_string_prop(self, propgroup, propname, value): + cmd = [PFCMD, SVCCFGCMD, "-s", self.instanceName, "setprop", \ + propgroup + '/' + propname, "=", "astring:", + "\"%s\"" % (value)] + util.run_command(cmd) + self.refresh_service() + + def set_boolean_prop(self, propgroup, propname, value): + if value == True: + strval = "true" + else: + strval = "false" + self.set_prop(propgroup, propname, "boolean", strval) + + def set_integer_prop(self, propgroup, propname, value): + self.set_prop(propgroup, propname, "integer", str(value)) + + def refresh_service(self): + cmd = [PFCMD, SVCADMCMD, "refresh", self.instanceName] + p = subprocess.Popen(cmd, close_fds=True) + + def disable_service (self): + if self.svcstate == "disabled": + return + cmd = [PFCMD, SVCADMCMD, "disable", self.instanceName] + p = subprocess.Popen(cmd, close_fds=True) + self.svcstate = self.get_service_state() + + def enable_service (self): + if (self.svcstate == "online" or self.svcstate == "degraded"): + return + cmd = [PFCMD, SVCADMCMD, "enable", self.instanceName] + p = subprocess.Popen(cmd, close_fds=True) + self.svcstate = self.get_service_state() + + def mark_maintenance (self): + cmd = [SVCADMCMD, "mark", "maintenance", self.instanceName] + subprocess.Popen(cmd, close_fds=True) + + def __str__(self): + ret = "SMF Instance:\n" +\ + "\tName:\t\t\t%s\n" % (self.instanceName) +\ + "\tState:\t\t\t%s\n" % (self.svcstate) + return ret + + +if __name__ == "__main__": + S = SMFInstance('svc:/application/time-slider') + print S + diff --git a/usr/share/time-slider/lib/time_slider/snapnowui.py b/usr/share/time-slider/lib/time_slider/snapnowui.py new file mode 100755 index 0000000..9e6c7c4 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/snapnowui.py @@ -0,0 +1,224 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +import os +import datetime +import getopt +import string + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import glib + import gobject +except: + sys.exit(1) + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from rbac import RBACprofile + +class SnapshotNowDialog: + + def __init__(self, dir_path, zfs_fs): + self.dir_path = dir_path + self.zfs_fs = zfs_fs + self.xml = gtk.glade.XML("%s/../../glade/time-slider-snapshot.glade" \ + % (os.path.dirname(__file__))) + self.dialog = self.xml.get_widget("dialog") + self.dir_label = self.xml.get_widget("dir_label") + self.snap_name_entry = self.xml.get_widget("snapshot_name_entry") + # signal dictionary + dic = {"on_closebutton_clicked" : gtk.main_quit, + "on_window_delete_event" : gtk.main_quit, + "on_cancel_clicked" : gtk.main_quit, + "on_ok_clicked" : self.__on_ok_clicked} + self.xml.signal_autoconnect(dic) + + self.snap_name_entry.connect("activate", self.__on_entry_activate, 0) + + self.dir_label.set_text(self.dir_path) + self.snap_name_entry.set_text("my-snapshot-%s" % datetime.datetime.now().strftime("%Y-%m-%d_%Hh%M:%S")) + + self.dialog.show () + + def validate_name (self, name, showErrorDialog=False): + #check name validity + # from http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/common/zfs/zfs_namecheck.c#dataset_namecheck + # http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/common/zfs/zfs_namecheck.c#valid_char + + invalid = False + _validchars = string.ascii_letters + string.digits + \ + "-_.:" + _allchars = string.maketrans("", "") + _invalidchars = _allchars.translate(_allchars, _validchars) + + valid_name = "" + + for c in name: + if c not in _invalidchars: + valid_name = valid_name + c + else: + invalid = True + + if invalid and showErrorDialog: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Invalid characters in snapshot name")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(_("Allowed characters for snapshot names are :\n" + "[a-z][A-Z][0-9][-_.:\n" + "All invalid characters will be removed\n")) + dialog.run () + dialog.destroy () + return valid_name + + + def __on_entry_activate (self, widget, none): + self.snap_name_entry.set_text (self.validate_name (self.snap_name_entry.get_text(), True)) + return + + + def __on_ok_clicked (self, widget): + name = self.snap_name_entry.get_text() + valid_name = self.validate_name (name, True) + if name == valid_name: + cmd = "pfexec /usr/sbin/zfs snapshot %s@%s" % (self.zfs_fs, self.validate_name (self.snap_name_entry.get_text())) + fin,fout,ferr = os.popen3(cmd) + # Check for any error output generated and + # return it to caller if so. + error = ferr.read() + self.dialog.hide () + if len(error) > 0: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Error occured while creating the snapshot")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(error) + dialog.run () + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_INFO, + gtk.BUTTONS_CLOSE, + _("Snapshot created successfully")) + dialog.set_title (_("Success")) + dialog.format_secondary_text(_("A snapshot of zfs filesystem %(zfs_fs)s\n" + "named %(valid_name)s\n" + "has been created.\n") % + { "zfs_fs" : self.zfs_fs, "valid_name" : valid_name}) + dialog.run () + + sys.exit(1) + else: + self.snap_name_entry.set_text (valid_name) + +def main(argv): + try: + opts,args = getopt.getopt(sys.argv[1:], "", []) + except getopt.GetoptError: + sys.exit(2) + #FIXME + #check for 2 args here we assume the arguments are correct + if len(args) != 2: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Invalid arguments count.")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(_("Snapshot Now requires" + " 2 arguments :\n- The path of the " + "directory to be snapshotted.\n" + "- The zfs filesystem corresponding " + "to this directory.")) + dialog.run() + sys.exit (2) + + rbacp = RBACprofile() + # The user security attributes checked are the following: + # 1. The "Primary Administrator" role + # 4. The "ZFS Files System Management" profile. + # + # Valid combinations of the above are: + # - 1 or 4 + # Note that an effective UID=0 will match any profile search so + # no need to check it explicitly. + if rbacp.has_profile("ZFS File System Management"): + manager = SnapshotNowDialog(args[0],args[1]) + gtk.main() + elif os.path.exists(argv) and os.path.exists("/usr/bin/gksu"): + # Run via gksu, which will prompt for the root password + newargs = ["gksu", argv] + for arg in args: + newargs.append(arg) + os.execv("/usr/bin/gksu", newargs) + # Shouldn't reach this point + sys.exit(1) + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Insufficient Priviliges")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(_("Snapshot Now requires " + "administrative privileges to run. " + "You have not been assigned the necessary" + "administrative priviliges." + "\n\nConsult your system administrator ")) + dialog.run() + print argv + "is not a valid executable path" + sys.exit(1) + diff --git a/usr/share/time-slider/lib/time_slider/snapnowui.py~ b/usr/share/time-slider/lib/time_slider/snapnowui.py~ new file mode 100755 index 0000000..9e6c7c4 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/snapnowui.py~ @@ -0,0 +1,224 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +import os +import datetime +import getopt +import string + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import glib + import gobject +except: + sys.exit(1) + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from rbac import RBACprofile + +class SnapshotNowDialog: + + def __init__(self, dir_path, zfs_fs): + self.dir_path = dir_path + self.zfs_fs = zfs_fs + self.xml = gtk.glade.XML("%s/../../glade/time-slider-snapshot.glade" \ + % (os.path.dirname(__file__))) + self.dialog = self.xml.get_widget("dialog") + self.dir_label = self.xml.get_widget("dir_label") + self.snap_name_entry = self.xml.get_widget("snapshot_name_entry") + # signal dictionary + dic = {"on_closebutton_clicked" : gtk.main_quit, + "on_window_delete_event" : gtk.main_quit, + "on_cancel_clicked" : gtk.main_quit, + "on_ok_clicked" : self.__on_ok_clicked} + self.xml.signal_autoconnect(dic) + + self.snap_name_entry.connect("activate", self.__on_entry_activate, 0) + + self.dir_label.set_text(self.dir_path) + self.snap_name_entry.set_text("my-snapshot-%s" % datetime.datetime.now().strftime("%Y-%m-%d_%Hh%M:%S")) + + self.dialog.show () + + def validate_name (self, name, showErrorDialog=False): + #check name validity + # from http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/common/zfs/zfs_namecheck.c#dataset_namecheck + # http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/common/zfs/zfs_namecheck.c#valid_char + + invalid = False + _validchars = string.ascii_letters + string.digits + \ + "-_.:" + _allchars = string.maketrans("", "") + _invalidchars = _allchars.translate(_allchars, _validchars) + + valid_name = "" + + for c in name: + if c not in _invalidchars: + valid_name = valid_name + c + else: + invalid = True + + if invalid and showErrorDialog: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Invalid characters in snapshot name")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(_("Allowed characters for snapshot names are :\n" + "[a-z][A-Z][0-9][-_.:\n" + "All invalid characters will be removed\n")) + dialog.run () + dialog.destroy () + return valid_name + + + def __on_entry_activate (self, widget, none): + self.snap_name_entry.set_text (self.validate_name (self.snap_name_entry.get_text(), True)) + return + + + def __on_ok_clicked (self, widget): + name = self.snap_name_entry.get_text() + valid_name = self.validate_name (name, True) + if name == valid_name: + cmd = "pfexec /usr/sbin/zfs snapshot %s@%s" % (self.zfs_fs, self.validate_name (self.snap_name_entry.get_text())) + fin,fout,ferr = os.popen3(cmd) + # Check for any error output generated and + # return it to caller if so. + error = ferr.read() + self.dialog.hide () + if len(error) > 0: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Error occured while creating the snapshot")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(error) + dialog.run () + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_INFO, + gtk.BUTTONS_CLOSE, + _("Snapshot created successfully")) + dialog.set_title (_("Success")) + dialog.format_secondary_text(_("A snapshot of zfs filesystem %(zfs_fs)s\n" + "named %(valid_name)s\n" + "has been created.\n") % + { "zfs_fs" : self.zfs_fs, "valid_name" : valid_name}) + dialog.run () + + sys.exit(1) + else: + self.snap_name_entry.set_text (valid_name) + +def main(argv): + try: + opts,args = getopt.getopt(sys.argv[1:], "", []) + except getopt.GetoptError: + sys.exit(2) + #FIXME + #check for 2 args here we assume the arguments are correct + if len(args) != 2: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Invalid arguments count.")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(_("Snapshot Now requires" + " 2 arguments :\n- The path of the " + "directory to be snapshotted.\n" + "- The zfs filesystem corresponding " + "to this directory.")) + dialog.run() + sys.exit (2) + + rbacp = RBACprofile() + # The user security attributes checked are the following: + # 1. The "Primary Administrator" role + # 4. The "ZFS Files System Management" profile. + # + # Valid combinations of the above are: + # - 1 or 4 + # Note that an effective UID=0 will match any profile search so + # no need to check it explicitly. + if rbacp.has_profile("ZFS File System Management"): + manager = SnapshotNowDialog(args[0],args[1]) + gtk.main() + elif os.path.exists(argv) and os.path.exists("/usr/bin/gksu"): + # Run via gksu, which will prompt for the root password + newargs = ["gksu", argv] + for arg in args: + newargs.append(arg) + os.execv("/usr/bin/gksu", newargs) + # Shouldn't reach this point + sys.exit(1) + else: + dialog = gtk.MessageDialog(None, + 0, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + _("Insufficient Priviliges")) + dialog.set_title (_("Error")) + dialog.format_secondary_text(_("Snapshot Now requires " + "administrative privileges to run. " + "You have not been assigned the necessary" + "administrative priviliges." + "\n\nConsult your system administrator ")) + dialog.run() + print argv + "is not a valid executable path" + sys.exit(1) + diff --git a/usr/share/time-slider/lib/time_slider/timesliderd.py b/usr/share/time-slider/lib/time_slider/timesliderd.py new file mode 100755 index 0000000..eb2e341 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/timesliderd.py @@ -0,0 +1,977 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +import os +import subprocess +import re +import threading +import getopt +import syslog +import time +import datetime +import calendar +import signal + +import glib +import gobject +import dbus +import dbus.service +import dbus.mainloop +import dbus.mainloop.glib + +import dbussvc +import zfs +import smf +import timeslidersmf +import autosnapsmf +import plugin +from rbac import RBACprofile +import util + +_MINUTE = 60 +_HOUR = _MINUTE * 60 +_DAY = _HOUR * 24 +_WEEK = _DAY * 7 + + +# Status codes for actual zpool capacity levels. +# These are relative to the SMF property defined +# levels for: user, warning and emergenecy levels +STATUS_OK = 0 # Below user specified threshhold. Everything was OK +STATUS_WARNING = 1 # Above specified user threshold level +STATUS_CRITICAL = 2 # Above specified critical threshhold level +STATUS_EMERGENCY = 3 # Above specified emergency threshhold level + +intervals = {"weeks" : _WEEK, "days" : _DAY, "hours" : _HOUR, "minutes" : _MINUTE} + + +class SnapshotManager(threading.Thread): + + def __init__(self, bus): + # Used to wake up the run() method prematurely in the event + # of a SIGHUP/SMF refresh + self._conditionLock = threading.Condition(threading.RLock()) + # Used when schedules are being rebuilt or examined. + self._refreshLock = threading.Lock() + # Indicates that cleanup is in progress when locked + self._cleanupLock = threading.Lock() + self._datasets = zfs.Datasets() + # Indicates that schedules need to be rebuilt from scratch + self._stale = True + self._lastCleanupCheck = 0; + self._zpools = [] + self._poolstatus = {} + self._destroyedsnaps = [] + + # This is also checked during the refresh() method but we need + # to know it sooner for instantiation of the PluginManager + self._smf = timeslidersmf.TimeSliderSMF() + try: + self.verbose = self._smf.get_verbose() + except RuntimeError,message: + sys.stderr.write("Error determing whether debugging is enabled\n") + self.verbose = False + + self._dbus = dbussvc.AutoSnap(bus, + '/org/opensolaris/TimeSlider/autosnap', + self) + + self._plugin = plugin.PluginManager(self.verbose) + self.exitCode = smf.SMF_EXIT_OK + self.refresh() + + # Seems we're up and running OK. + # Signal our parent so we can daemonise + os.kill(os.getppid(), signal.SIGUSR1) + + # SMF/svc.startd sends SIGHUP to force a + # a refresh of the daemon + signal.signal(signal.SIGHUP, self._signalled) + + # Init done. Now initiaslise threading. + threading.Thread.__init__ (self) + self.setDaemon(True) + + def run(self): + # Deselect swap and dump volumes so they don't get snapshotted. + for vol in self._datasets.list_volumes(): + name = vol.rsplit("/") + try: + if (name[1] == "swap" or name[1] == "dump"): + util.debug("Auto excluding %s volume" % vol, self.verbose) + volume = zfs.Volume(vol) + volume.set_auto_snap(False) + except IndexError: + pass + + nexttime = None + waittime = None + while True: + try: + self.refresh() + # First check and, if necessary, perform any remedial cleanup. + # This is best done before creating any new snapshots which may + # otherwise get immediately gobbled up by the remedial cleanup. + if self._needs_cleanup() == True: + self._perform_cleanup() + # Check to see if cleanup actually deleted anything before + # notifying the user. Avoids the popup appearing continuously + if len(self._destroyedsnaps) > 0: + self._send_notification() + self._send_to_syslog() + + nexttime = self._check_snapshots() + # Overdue snapshots are already taken automatically + # inside _check_snapshots() so nexttime should never be + # < 0. It can be None however, which is fine since it + # will cause the scheduler thread to sleep indefinitely + # or until a SIGHUP is caught. + if nexttime: + util.debug("Waiting until " + str (nexttime), self.verbose) + waittime = None + if nexttime != None: + waittime = nexttime - long(time.time()) + if (waittime <= 0): + # We took too long and missed a snapshot, so break out + # and catch up on it the next time through the loop + continue + # waittime could be None if no auto-snap schedules are online + self._conditionLock.acquire() + if waittime: + util.debug("Waiting %d seconds" % (waittime), self.verbose) + self._conditionLock.wait(waittime) + else: #None. Just wait a while to check for cleanups. + util.debug("No auto-snapshot schedules online.", \ + self.verbose) + self._conditionLock.wait(_MINUTE * 15) + + except OSError, message: + sys.stderr.write("Caught OSError exception in snapshot" + + " manager thread\n") + sys.stderr.write("Error details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n--------END ERROR MESSAGE--------\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Exit this thread + break + except RuntimeError,message: + sys.stderr.write("Caught RuntimeError exception in snapshot" + + " manager thread\n") + sys.stderr.write("Error details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n--------END ERROR MESSAGE--------\n") + # Exit this thread + break + + def _signalled(self, signum, frame): + if signum == signal.SIGHUP: + if self._refreshLock.acquire(False) == False: + return + self._stale = True + self._refreshLock.release() + self._conditionLock.acquire() + self._conditionLock.notify() + self._conditionLock.release() + + def refresh(self): + """ + Checks if defined snapshot schedules are out + of date and rebuilds and updates if necessary + """ + self._refreshLock.acquire() + if self._stale == True: + self._configure_svc_props() + self._rebuild_schedules() + self._update_schedules() + self._plugin.refresh() + self._stale = False + self._refreshLock.release() + + def _configure_svc_props(self): + try: + self.verbose = self._smf.get_verbose() + except RuntimeError,message: + sys.stderr.write("Error determing whether debugging is enabled\n") + self.verbose = False + + try: + cleanup = self._smf.get_remedial_cleanup() + warn = self._smf.get_cleanup_level("warning") + util.debug("Warning level value is: %d%%" % warn, self.verbose) + crit = self._smf.get_cleanup_level("critical") + util.debug("Critical level value is: %d%%" % crit, self.verbose) + emer = self._smf.get_cleanup_level("emergency") + util.debug("Emergency level value is: %d%%" % emer, self.verbose) + except RuntimeError,message: + sys.stderr.write("Failed to determine cleanup threshhold levels\n") + sys.stderr.write("Details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n---------END ERROR MESSAGE---------\n") + sys.stderr.write("Using factory defaults of 80%, 90% and 95%\n") + #Go with defaults + #FIXME - this would be an appropriate case to mark svc as degraded + self._remedialCleanup = True + self._warningLevel = 80 + self._criticalLevel = 90 + self._emergencyLevel = 95 + else: + self._remedialCleanup = cleanup + self._warningLevel = warn + self._criticalLevel = crit + self._emergencyLevel = emer + + try: + self._keepEmpties = self._smf.get_keep_empties() + except RuntimeError,message: + # Not fatal, just assume we delete them (default configuration) + sys.stderr.write("Can't determine whether to keep empty snapshots\n") + sys.stderr.write("Details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n---------END ERROR MESSAGE---------\n") + sys.stderr.write("Assuming default value: False\n") + self._keepEmpties = False + + # Previously, snapshot labels used the ":" character was used as a + # separator character for datestamps. Windows filesystems such as + # CIFS and FAT choke on this character so now we use a user definable + # separator value, with a default value of "_" + # We need to check for both the old and new format when looking for + # snapshots. + self._separator = self._smf.get_separator() + self._prefix = "%s[:%s]" \ + % (autosnapsmf.SNAPLABELPREFIX, self._separator) + + # Rebuild pool list + self._zpools = [] + try: + for poolname in zfs.list_zpools(): + # Do not try to examine FAULTED pools + zpool = zfs.ZPool(poolname) + if zpool.health == "FAULTED": + util.debug("Ignoring faulted Zpool: %s\n" \ + % (zpool.name), \ + self.verbose) + else: + self._zpools.append(zpool) + util.debug(str(zpool), self.verbose) + except RuntimeError,message: + sys.stderr.write("Could not list Zpools\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate exception up to thread's run() method + raise RuntimeError,message + + + def _rebuild_schedules(self): + """ + Builds 2 lists of default and custom auto-snapshot SMF instances + """ + + self._last = {} + self._next = {} + self._keep = {} + + try: + _defaultSchedules = autosnapsmf.get_default_schedules() + _customSchedules = autosnapsmf.get_custom_schedules() + except RuntimeError,message: + self.exitCode = smf.SMF_EXIT_ERR_FATAL + raise RuntimeError, "Error reading SMF schedule instances\n" + \ + "Details:\n" + str(message) + else: + # Now set it in stone. + self._defaultSchedules = tuple(_defaultSchedules) + self._customSchedules = tuple(_customSchedules) + + # Build the combined schedule tuple from default + custom schedules + _defaultSchedules.extend(_customSchedules) + self._allSchedules = tuple(_defaultSchedules) + for schedule,i,p,keep in self._allSchedules: + self._last[schedule] = 0 + self._next[schedule] = 0 + self._keep[schedule] = keep + + def _update_schedules(self): + interval = 0 + idx = 1 # Used to index subsets for schedule overlap calculation + last = None + + for schedule,interval,period,keep in self._allSchedules: + # Shortcut if we've already processed this schedule and it's + # still up to date. Don't skip the default schedules though + # because overlap affects their scheduling + if [schedule,interval,period,keep] not in \ + self._defaultSchedules and \ + (self._next[schedule] > self._last[schedule]): + util.debug("Short circuiting %s recalculation" \ + % (schedule), \ + self.verbose) + continue + + # If we don't have an internal timestamp for the given schedule + # ask zfs for the last snapshot and get it's creation timestamp. + if self._last[schedule] == 0: + try: + snaps = self._datasets.list_snapshots("%s%s" % \ + (self._prefix, + schedule)) + except RuntimeError,message: + self.exitCode = smf.SMF_EXIT_ERR_FATAL + sys.stderr.write("Failed to list snapshots during schedule update\n") + #Propogate up to the thread's run() method + raise RuntimeError,message + + if len(snaps) > 0: + util.debug("Last %s snapshot was: %s" % \ + (schedule, snaps[-1][0]), \ + self.verbose) + self._last[schedule] = snaps[-1][1] + + last = self._last[schedule] + if interval != "months": # months is non-constant. See below. + util.debug("Recalculating %s schedule" % (schedule), \ + self.verbose) + try: + totalinterval = intervals[interval] * period + except KeyError: + self.exitCode = smf.SMF_EXIT_ERR_CONFIG + sys.stderr.write(schedule + \ + " schedule has invalid interval: " + \ + "'%s\'\n" % interval) + #Propogate up to thread's run() method + raise RuntimeError + if [schedule,interval,period,keep] in self._defaultSchedules: + # This is one of the default schedules so check for an + # overlap with one of the dominant shchedules. + for s,i,p,k in self._defaultSchedules[:idx]: + last = max(last, self._last[s]) + idx += 1 + + else: # interval == "months" + if self._next[schedule] > last: + util.debug("Short circuiting " + \ + schedule + \ + " recalculation", \ + self.verbose) + continue + util.debug("Recalculating %s schedule" % (schedule), \ + self.verbose) + snap_tm = time.gmtime(self._last[schedule]) + # Increment year if period >= than 1 calender year. + year = snap_tm.tm_year + year += period / 12 + period = period % 12 + + mon = (snap_tm.tm_mon + period) % 12 + # Result of 0 actually means december. + if mon == 0: + mon = 12 + # Account for period that spans calendar year boundary. + elif snap_tm.tm_mon + period > 12: + year += 1 + + d,dlastmon = calendar.monthrange(snap_tm.tm_year, snap_tm.tm_mon) + d,dnewmon = calendar.monthrange(year, mon) + mday = snap_tm.tm_mday + if dlastmon > dnewmon and snap_tm.tm_mday > dnewmon: + mday = dnewmon + + tm =(year, mon, mday, \ + snap_tm.tm_hour, snap_tm.tm_min, snap_tm.tm_sec, \ + 0, 0, -1) + newt = calendar.timegm(tm) + new_tm = time.gmtime(newt) + totalinterval = newt - self._last[schedule] + + self._next[schedule] = last + totalinterval + + def _next_due(self): + schedule = None + earliest = None + now = long(time.time()) + + for s,i,p,k in self._defaultSchedules: + due = self._next[s] + if due <= now: + #Default Schedule - so break out at the first + #schedule that is overdue. The subordinate schedules + #will re-adjust afterwards. + earliest,schedule = due,s + break + elif earliest != None: + if due < earliest: + earliest,schedule = due,s + else: #FIXME better optimisation with above condition + earliest,schedule = due,s + for s,i,p,k in self._customSchedules: + due = self._next[s] + if earliest != None: + if due < earliest: + earliest,schedule = due,s + else: #FIXME better optimisation with above condition + earliest,schedule = due,s + return earliest,schedule + + def _check_snapshots(self): + """ + Check the schedules and see what the required snapshot is. + Take one immediately on the first overdue snapshot required + """ + # Make sure a refresh() doesn't mess with the schedule while + # we're reading through it. + self._refreshLock.acquire() + next,schedule = self._next_due() + self._refreshLock.release() + now = long(time.time()) + while next != None and next <= now: + label = self._take_snapshots(schedule) + self._plugin.execute_plugins(schedule, label) + self._refreshLock.acquire() + self._update_schedules() + next,schedule = self._next_due(); + self._refreshLock.release() + dt = datetime.datetime.fromtimestamp(next) + util.debug("Next snapshot is %s due at: %s" % \ + (schedule, dt.isoformat()), \ + self.verbose) + return next + + def _take_snapshots(self, schedule): + # Set the time before taking snapshot to avoid clock skew due + # to time taken to complete snapshot. + tm = long(time.time()) + label = "%s%s%s-%s" % \ + (autosnapsmf.SNAPLABELPREFIX, self._separator, schedule, + datetime.datetime.now().strftime("%Y-%m-%d-%Hh%M")) + try: + self._datasets.create_auto_snapshot_set(label, tag=schedule) + except RuntimeError, message: + # Write an error message, set the exit code and pass it up the + # stack so the thread can terminate + sys.stderr.write("Failed to create snapshots for schedule: %s\n" \ + % (schedule)) + self.exitCode = smf.SMF_EXIT_MON_DEGRADE + raise RuntimeError,message + self._last[schedule] = tm; + self._perform_purge(schedule) + return label + + def _prune_snapshots(self, dataset, schedule): + """Cleans out zero sized snapshots, kind of cautiously""" + # Per schedule: We want to delete 0 sized + # snapshots but we need to keep at least one around (the most + # recent one) for each schedule so that that overlap is + # maintained from frequent -> hourly -> daily etc. + # Start off with the smallest interval schedule first and + # move up. This increases the amount of data retained where + # several snapshots are taken together like a frequent hourly + # and daily snapshot taken at 12:00am. If 3 snapshots are all + # identical and reference the same identical data they will all + # be initially reported as zero for used size. Deleting the + # daily first then the hourly would shift make the data referenced + # by all 3 snapshots unique to the frequent scheduled snapshot. + # This snapshot would probably be purged within an how ever and the + # data referenced by it would be gone for good. + # Doing it the other way however ensures that the data should + # remain accessible to the user for at least a week as long as + # the pool doesn't run low on available space before that. + + try: + snaps = dataset.list_snapshots("%s%s" % (self._prefix,schedule)) + # Clone the list because we want to remove items from it + # while iterating through it. + remainingsnaps = snaps[:] + except RuntimeError,message: + sys.stderr.write("Failed to list snapshots during snapshot cleanup\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + raise RuntimeError,message + + if (self._keepEmpties == False): + try: # remove the newest one from the list. + snaps.pop() + except IndexError: + pass + for snapname in snaps: + try: + snapshot = zfs.Snapshot(snapname) + except Exception,message: + sys.stderr.write(str(message)) + # Not fatal, just skip to the next snapshot + continue + + try: + if snapshot.get_used_size() == 0: + util.debug("Destroying zero sized: " + snapname, \ + self.verbose) + try: + snapshot.destroy() + except RuntimeError,message: + sys.stderr.write("Failed to destroy snapshot: " + + snapname + "\n") + self.exitCode = smf.SMF_EXIT_MON_DEGRADE + # Propogate exception so thread can exit + raise RuntimeError,message + remainingsnaps.remove(snapname) + except RuntimeError,message: + sys.stderr.write("Can not determine used size of: " + \ + snapname + "\n") + self.exitCode = smf.SMF_EXIT_MON_DEGRADE + #Propogate the exception to the thead run() method + raise RuntimeError,message + + # Deleting individual snapshots instead of recursive sets + # breaks the recursion chain and leaves child snapshots + # dangling so we need to take care of cleaning up the + # snapshots. + target = len(remainingsnaps) - self._keep[schedule] + counter = 0 + while counter < target: + util.debug("Destroy expired snapshot: " + \ + remainingsnaps[counter], + self.verbose) + try: + snapshot = zfs.Snapshot(remainingsnaps[counter]) + except Exception,message: + sys.stderr.write(str(message)) + # Not fatal, just skip to the next snapshot + counter += 1 + continue + try: + snapshot.destroy() + except RuntimeError,message: + sys.stderr.write("Failed to destroy snapshot: " + + snapshot.name + "\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate exception so thread can exit + raise RuntimeError,message + else: + counter += 1 + + def _perform_purge(self, schedule): + """Cautiously cleans out zero sized snapshots""" + # We need to avoid accidentally pruning auto snapshots received + # from one zpool to another. We ensure this by examining only + # snapshots whose parent fileystems and volumes are explicitly + # tagged to be snapshotted. + try: + for name in self._datasets.list_auto_snapshot_sets(schedule): + dataset = zfs.ReadWritableDataset(name) + self._prune_snapshots(dataset, schedule) + except RuntimeError,message: + sys.stderr.write("Error listing datasets during " + \ + "removal of expired snapshots\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate up to thread's run() method + raise RuntimeError,message + + def _needs_cleanup(self): + if self._remedialCleanup == False: + # Sys admin has explicitly instructed for remedial cleanups + # not to be performed. + return False + now = long(time.time()) + # Don't run checks any less than 15 minutes apart. + if self._cleanupLock.acquire(False) == False: + #Indicates that a cleanup is already running. + return False + # FIXME - Make the cleanup interval equal to the minimum snapshot interval + # if custom snapshot schedules are defined and enabled. + elif ((now - self._lastCleanupCheck) < (_MINUTE * 15)): + pass + else: + for zpool in self._zpools: + try: + if zpool.get_capacity() > self._warningLevel: + # Before getting into a panic, determine if the pool + # is one we actually take snapshots on, by checking + # for one of the "auto-snapshot: tags. Not + # super fast, but it only happens under exceptional + # circumstances of a zpool nearing it's capacity. + + for sched in self._allSchedules: + sets = zpool.list_auto_snapshot_sets(sched[0]) + if len(sets) > 0: + util.debug("%s needs a cleanup" \ + % zpool.name, \ + self.verbose) + self._cleanupLock.release() + return True + except RuntimeError, message: + sys.stderr.write("Error checking zpool capacity of: " + \ + zpool.name + "\n") + self._cleanupLock.release() + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate up to thread's run() mehod. + raise RuntimeError,message + self._lastCleanupCheck = long(time.time()) + self._cleanupLock.release() + return False + + def _perform_cleanup(self): + if self._cleanupLock.acquire(False) == False: + # Cleanup already running. Skip + return + self._destroyedsnaps = [] + for zpool in self._zpools: + try: + self._poolstatus[zpool.name] = 0 + capacity = zpool.get_capacity() + if capacity > self._warningLevel: + self._run_warning_cleanup(zpool) + self._poolstatus[zpool.name] = 1 + capacity = zpool.get_capacity() + if capacity > self._criticalLevel: + self._run_critical_cleanup(zpool) + self._poolstatus[zpool.name] = 2 + capacity = zpool.get_capacity() + if capacity > self._emergencyLevel: + self._run_emergency_cleanup(zpool) + self._poolstatus[zpool.name] = 3 + capacity = zpool.get_capacity() + if capacity > self._emergencyLevel: + self._run_emergency_cleanup(zpool) + self._poolstatus[zpool.name] = 4 + # This also catches exceptions thrown from _run__cleanup() + # and _run_cleanup() in methods called by _perform_cleanup() + except RuntimeError,message: + sys.stderr.write("Remedial space cleanup failed because " + \ + "of failure to determinecapacity of: " + \ + zpool.name + "\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + self._cleanupLock.release() + # Propogate up to thread's run() method. + raise RuntimeError,message + + # Bad - there's no more snapshots left and nothing + # left to delete. We don't disable the service since + # it will permit self recovery and snapshot + # retention when space becomes available on + # the pool (hopefully). + util.debug("%s pool status after cleanup:" \ + % zpool.name, \ + self.verbose) + util.debug(zpool, self.verbose) + util.debug("Cleanup completed. %d snapshots were destroyed" \ + % len(self._destroyedsnaps), \ + self.verbose) + # Avoid needless list iteration for non-debug mode + if self.verbose == True and len(self._destroyedsnaps) > 0: + for snap in self._destroyedsnaps: + sys.stderr.write("\t%s\n" % snap) + self._cleanupLock.release() + + def _run_warning_cleanup(self, zpool): + util.debug("Performing warning level cleanup on %s" % \ + zpool.name, \ + self.verbose) + self._run_cleanup(zpool, "daily", self._warningLevel) + if zpool.get_capacity() > self._warningLevel: + self._run_cleanup(zpool, "hourly", self._warningLevel) + + def _run_critical_cleanup(self, zpool): + util.debug("Performing critical level cleanup on %s" % \ + zpool.name, \ + self.verbose) + self._run_cleanup(zpool, "weekly", self._criticalLevel) + if zpool.get_capacity() > self._criticalLevel: + self._run_cleanup(zpool, "daily", self._criticalLevel) + if zpool.get_capacity() > self._criticalLevel: + self._run_cleanup(zpool, "hourly", self._criticalLevel) + + def _run_emergency_cleanup(self, zpool): + util.debug("Performing emergency level cleanup on %s" % \ + zpool.name, \ + self.verbose) + self._run_cleanup(zpool, "monthly", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "weekly", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "daily", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "hourly", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "frequent", self._emergencyLevel) + #Finally, as a last resort, delete custom scheduled snaphots + for schedule,i,p,k in self._customSchedules: + if zpool.get_capacity() < self._emergencyLevel: + break + else: + self._run_cleanup(zpool, schedule, self._emergencyLevel) + + def _run_cleanup(self, zpool, schedule, threshold): + clonedsnaps = [] + snapshots = [] + try: + clonedsnaps = self._datasets.list_cloned_snapshots() + except RuntimeError,message: + sys.stderr.write("Error (non-fatal) listing cloned snapshots" + + " while recovering pool capacity\n") + sys.stderr.write("Error details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n--------END ERROR MESSAGE--------\n") + + # Build a list of snapshots in the given schedule, that are not + # cloned, and sort the result in reverse chronological order. + try: + snapshots = [s for s,t in \ + zpool.list_snapshots("%s%s" \ + % (self._prefix,schedule)) \ + if not s in clonedsnaps] + snapshots.reverse() + except RuntimeError,message: + sys.stderr.write("Error listing snapshots" + + " while recovering pool capacity\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate the error up to the thread's run() method. + raise RuntimeError,message + + while zpool.get_capacity() > threshold: + if len(snapshots) == 0: + syslog.syslog(syslog.LOG_NOTICE, + "No more %s snapshots left" \ + % schedule) + return + + """This is not an exact science. Deleteing a zero sized + snapshot can have unpredictable results. For example a + pair of snapshots may share exclusive reference to a large + amount of data (eg. a large core file). The usage of both + snapshots will initially be seen to be 0 by zfs(1). Deleting + one of the snapshots will make the data become unique to the + single remaining snapshot that references it uniquely. The + remaining snapshot's size will then show up as non zero. So + deleting 0 sized snapshot is not as pointless as it might seem. + It also means we have to loop through this, each snapshot set + at a time and observe the before and after results. Perhaps + better way exists....""" + + # Start with the oldest first + snapname = snapshots.pop() + snapshot = zfs.Snapshot(snapname) + # It would be nicer, for performance purposes, to delete sets + # of snapshots recursively but this might destroy more data than + # absolutely necessary, plus the previous purging of zero sized + # snapshots can easily break the recursion chain between + # filesystems. + # On the positive side there should be fewer snapshots and they + # will mostly non-zero so we should get more effectiveness as a + # result of deleting snapshots since they should be nearly always + # non zero sized. + util.debug("Destroying %s" % snapname, self.verbose) + try: + snapshot.destroy() + except RuntimeError,message: + # Would be nice to be able to mark service as degraded here + # but it's better to try to continue on rather than to give + # up alltogether (SMF maintenance state) + sys.stderr.write("Warning: Cleanup failed to destroy: %s\n" % \ + (snapshot.name)) + sys.stderr.write("Details:\n%s\n" % (str(message))) + else: + self._destroyedsnaps.append(snapname) + # Give zfs some time to recalculate. + time.sleep(3) + + def _send_to_syslog(self): + for zpool in self._zpools: + status = self._poolstatus[zpool.name] + if status == 4: + syslog.syslog(syslog.LOG_EMERG, + "%s is over %d%% capacity. " \ + "All automatic snapshots were destroyed" \ + % (zpool.name, self._emergencyLevel)) + elif status == 3: + syslog.syslog(syslog.LOG_ALERT, + "%s exceeded %d%% capacity. " \ + "Automatic snapshots over 1 hour old were destroyed" \ + % (zpool.name, self._emergencyLevel)) + elif status == 2: + syslog.syslog(syslog.LOG_CRIT, + "%s exceeded %d%% capacity. " \ + "Weekly, hourly and daily automatic snapshots were destroyed" \ + % (zpool.name, self._criticalLevel)) + elif status == 1: + syslog.syslog(syslog.LOG_WARNING, + "%s exceeded %d%% capacity. " \ + "Hourly and daily automatic snapshots were destroyed" \ + % (zpool.name, self._warningLevel)) + + if len(self._destroyedsnaps) > 0: + syslog.syslog(syslog.LOG_NOTICE, + "%d automatic snapshots were destroyed" \ + % len(self._destroyedsnaps)) + + def _send_notification(self): + worstpool = None + worststatus = 0 + + for zpool in self._zpools: + status = self._poolstatus[zpool.name] + # >= to ensure that something should always be set. + if status >= worststatus: + worstpool = zpool.name + worststatus = status + + #FIXME make the various levels indexible + if worststatus == 4: + self._dbus.capacity_exceeded(worstpool, 4, self._emergencyLevel) + elif worststatus == 3: + self._dbus.capacity_exceeded(worstpool, 3, self._emergencyLevel) + elif worststatus == 2: + self._dbus.capacity_exceeded(worstpool, 2, self._criticalLevel) + elif worststatus == 1: + self._dbus.capacity_exceeded(worstpool, 1, self._warningLevel) + #elif: 0 everything is fine. Do nothing. + + +def monitor_threads(snapthread): + if snapthread.is_alive(): + return True + else: + sys.stderr.write("Snapshot monitor thread exited.\n") + if snapthread.exitCode == smf.SMF_EXIT_MON_DEGRADE: + # FIXME - it would be nicer to mark the service as degraded than + # go into maintenance state for some situations such as a + # particular snapshot schedule failing. + # But for now SMF does not implement this feature. But if/when it + # does it's better to use svcadm to put the # service into the + # correct state since the daemon shouldn't exit whentransitioning + # to a degraded state. + #sys.stderr.write("Placing service into maintenance state\n") + #subprocess.call(["/usr/sbin/svcadm", "mark", "maintenance", + # os.getenv("SMF_FMRI")]) + # SMF will take care of kill the daemon + sys.exit(smf.SMF_EXIT_ERR_FATAL) + return False + elif snapthread.exitCode == smf.SMF_EXIT_ERR_FATAL: + #sys.stderr.write("Placing service into maintenance state\n") + #subprocess.call(["/usr/sbin/svcadm", "mark", "maintenance", + # os.getenv("SMF_FMRI")]) + # SMF will take care of killing the daemon + sys.exit(smf.SMF_EXIT_ERR_FATAL) + return False + else: + sys.stderr.write("Snapshot monitor thread exited abnormally\n") + sys.stderr.write("Exit code: %d\n" % (snapthread.exitCode)) + #subprocess.call(["/usr/sbin/svcadm", "mark", "maintenance", + # os.getenv("SMF_FMRI")]) + sys.exit(smf.SMF_EXIT_ERR_FATAL) + return False + + +def child_sig_handler(signum, frame): + if signum == signal.SIGUSR1: + sys.exit(smf.SMF_EXIT_OK) + elif signum == signal.SIGCHLD: + sys.exit(smf.SMF_EXIT_ERR_FATAL) + elif signum == signal.SIGALRM: + sys.exit(smf.SMF_EXIT_ERR_FATAL) + +# Default daemon parameters. +# File mode creation mask of the daemon. +UMASK = 0 +# Default working directory for the daemon. +WORKDIR = "/" +# Default maximum for the number of available file descriptors. +MAXFD = 1024 + +def create_daemon(): + """ + Detach a process from the controlling terminal and run it in the + background as a daemon. + """ + #Catch signals that we might receive from child + signal.signal(signal.SIGCHLD, child_sig_handler) + signal.signal(signal.SIGUSR1, child_sig_handler) + signal.signal(signal.SIGALRM, child_sig_handler) + try: + pid = os.fork() + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if (pid == 0): + #Reset signals that we set to trap in parent + signal.signal(signal.SIGCHLD, signal.SIG_DFL) + signal.signal(signal.SIGUSR1, signal.SIG_DFL) + signal.signal(signal.SIGALRM, signal.SIG_DFL) + os.setsid() + os.chdir(WORKDIR) + os.umask(UMASK) + else: + #Wait for the child to give the OK or otherwise. + signal.pause() + + +def main(argv): + + # Check SMF invocation environment + if os.getenv("SMF_FMRI") == None or os.getenv("SMF_METHOD") != "start": + sys.stderr.write("Command line invocation of %s unsupported.\n" \ + % (sys.argv[0])) + sys.stderr.write("This command is intended for smf(5) invocation only.\n") + sys.exit(smf.SMF_EXIT_ERR_NOSMF) + + # Daemonise the service. + create_daemon() + + # The user security attributes checked are the following: + # Note that UID == 0 will match any profile search so + # no need to check it explicitly. + syslog.openlog("time-sliderd", 0, syslog.LOG_DAEMON) + rbacp = RBACprofile() + if rbacp.has_profile("ZFS File System Management"): + + gobject.threads_init() + + # Tell dbus to use the gobject mainloop for async ops + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + dbus.mainloop.glib.threads_init() + # Register a bus name with the system dbus daemon + systemBus = dbus.SystemBus() + name = dbus.service.BusName("org.opensolaris.TimeSlider", systemBus) + + # Create and start the snapshot manger. Takes care of + # auto snapshotting service and auto cleanup. + snapshot = SnapshotManager(systemBus) + snapshot.start() + gobject.timeout_add(2000, monitor_threads, snapshot) + + mainloop = gobject.MainLoop() + try: + mainloop.run() + except KeyboardInterrupt: + mainloop.quit() + sys.exit(smf.SMF_EXIT_OK) + else: + syslog.syslog(syslog.LOG_ERR, + "%s has insufficient privileges to run time-sliderd!" \ + % rbacp.name) + syslog.closelog() + sys.exit(smf.SMF_EXIT_ERR_PERM) + syslog.closelog() + sys.exit(smf.SMF_EXIT_OK) + diff --git a/usr/share/time-slider/lib/time_slider/timesliderd.py~ b/usr/share/time-slider/lib/time_slider/timesliderd.py~ new file mode 100755 index 0000000..eb2e341 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/timesliderd.py~ @@ -0,0 +1,977 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import sys +import os +import subprocess +import re +import threading +import getopt +import syslog +import time +import datetime +import calendar +import signal + +import glib +import gobject +import dbus +import dbus.service +import dbus.mainloop +import dbus.mainloop.glib + +import dbussvc +import zfs +import smf +import timeslidersmf +import autosnapsmf +import plugin +from rbac import RBACprofile +import util + +_MINUTE = 60 +_HOUR = _MINUTE * 60 +_DAY = _HOUR * 24 +_WEEK = _DAY * 7 + + +# Status codes for actual zpool capacity levels. +# These are relative to the SMF property defined +# levels for: user, warning and emergenecy levels +STATUS_OK = 0 # Below user specified threshhold. Everything was OK +STATUS_WARNING = 1 # Above specified user threshold level +STATUS_CRITICAL = 2 # Above specified critical threshhold level +STATUS_EMERGENCY = 3 # Above specified emergency threshhold level + +intervals = {"weeks" : _WEEK, "days" : _DAY, "hours" : _HOUR, "minutes" : _MINUTE} + + +class SnapshotManager(threading.Thread): + + def __init__(self, bus): + # Used to wake up the run() method prematurely in the event + # of a SIGHUP/SMF refresh + self._conditionLock = threading.Condition(threading.RLock()) + # Used when schedules are being rebuilt or examined. + self._refreshLock = threading.Lock() + # Indicates that cleanup is in progress when locked + self._cleanupLock = threading.Lock() + self._datasets = zfs.Datasets() + # Indicates that schedules need to be rebuilt from scratch + self._stale = True + self._lastCleanupCheck = 0; + self._zpools = [] + self._poolstatus = {} + self._destroyedsnaps = [] + + # This is also checked during the refresh() method but we need + # to know it sooner for instantiation of the PluginManager + self._smf = timeslidersmf.TimeSliderSMF() + try: + self.verbose = self._smf.get_verbose() + except RuntimeError,message: + sys.stderr.write("Error determing whether debugging is enabled\n") + self.verbose = False + + self._dbus = dbussvc.AutoSnap(bus, + '/org/opensolaris/TimeSlider/autosnap', + self) + + self._plugin = plugin.PluginManager(self.verbose) + self.exitCode = smf.SMF_EXIT_OK + self.refresh() + + # Seems we're up and running OK. + # Signal our parent so we can daemonise + os.kill(os.getppid(), signal.SIGUSR1) + + # SMF/svc.startd sends SIGHUP to force a + # a refresh of the daemon + signal.signal(signal.SIGHUP, self._signalled) + + # Init done. Now initiaslise threading. + threading.Thread.__init__ (self) + self.setDaemon(True) + + def run(self): + # Deselect swap and dump volumes so they don't get snapshotted. + for vol in self._datasets.list_volumes(): + name = vol.rsplit("/") + try: + if (name[1] == "swap" or name[1] == "dump"): + util.debug("Auto excluding %s volume" % vol, self.verbose) + volume = zfs.Volume(vol) + volume.set_auto_snap(False) + except IndexError: + pass + + nexttime = None + waittime = None + while True: + try: + self.refresh() + # First check and, if necessary, perform any remedial cleanup. + # This is best done before creating any new snapshots which may + # otherwise get immediately gobbled up by the remedial cleanup. + if self._needs_cleanup() == True: + self._perform_cleanup() + # Check to see if cleanup actually deleted anything before + # notifying the user. Avoids the popup appearing continuously + if len(self._destroyedsnaps) > 0: + self._send_notification() + self._send_to_syslog() + + nexttime = self._check_snapshots() + # Overdue snapshots are already taken automatically + # inside _check_snapshots() so nexttime should never be + # < 0. It can be None however, which is fine since it + # will cause the scheduler thread to sleep indefinitely + # or until a SIGHUP is caught. + if nexttime: + util.debug("Waiting until " + str (nexttime), self.verbose) + waittime = None + if nexttime != None: + waittime = nexttime - long(time.time()) + if (waittime <= 0): + # We took too long and missed a snapshot, so break out + # and catch up on it the next time through the loop + continue + # waittime could be None if no auto-snap schedules are online + self._conditionLock.acquire() + if waittime: + util.debug("Waiting %d seconds" % (waittime), self.verbose) + self._conditionLock.wait(waittime) + else: #None. Just wait a while to check for cleanups. + util.debug("No auto-snapshot schedules online.", \ + self.verbose) + self._conditionLock.wait(_MINUTE * 15) + + except OSError, message: + sys.stderr.write("Caught OSError exception in snapshot" + + " manager thread\n") + sys.stderr.write("Error details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n--------END ERROR MESSAGE--------\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Exit this thread + break + except RuntimeError,message: + sys.stderr.write("Caught RuntimeError exception in snapshot" + + " manager thread\n") + sys.stderr.write("Error details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n--------END ERROR MESSAGE--------\n") + # Exit this thread + break + + def _signalled(self, signum, frame): + if signum == signal.SIGHUP: + if self._refreshLock.acquire(False) == False: + return + self._stale = True + self._refreshLock.release() + self._conditionLock.acquire() + self._conditionLock.notify() + self._conditionLock.release() + + def refresh(self): + """ + Checks if defined snapshot schedules are out + of date and rebuilds and updates if necessary + """ + self._refreshLock.acquire() + if self._stale == True: + self._configure_svc_props() + self._rebuild_schedules() + self._update_schedules() + self._plugin.refresh() + self._stale = False + self._refreshLock.release() + + def _configure_svc_props(self): + try: + self.verbose = self._smf.get_verbose() + except RuntimeError,message: + sys.stderr.write("Error determing whether debugging is enabled\n") + self.verbose = False + + try: + cleanup = self._smf.get_remedial_cleanup() + warn = self._smf.get_cleanup_level("warning") + util.debug("Warning level value is: %d%%" % warn, self.verbose) + crit = self._smf.get_cleanup_level("critical") + util.debug("Critical level value is: %d%%" % crit, self.verbose) + emer = self._smf.get_cleanup_level("emergency") + util.debug("Emergency level value is: %d%%" % emer, self.verbose) + except RuntimeError,message: + sys.stderr.write("Failed to determine cleanup threshhold levels\n") + sys.stderr.write("Details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n---------END ERROR MESSAGE---------\n") + sys.stderr.write("Using factory defaults of 80%, 90% and 95%\n") + #Go with defaults + #FIXME - this would be an appropriate case to mark svc as degraded + self._remedialCleanup = True + self._warningLevel = 80 + self._criticalLevel = 90 + self._emergencyLevel = 95 + else: + self._remedialCleanup = cleanup + self._warningLevel = warn + self._criticalLevel = crit + self._emergencyLevel = emer + + try: + self._keepEmpties = self._smf.get_keep_empties() + except RuntimeError,message: + # Not fatal, just assume we delete them (default configuration) + sys.stderr.write("Can't determine whether to keep empty snapshots\n") + sys.stderr.write("Details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n---------END ERROR MESSAGE---------\n") + sys.stderr.write("Assuming default value: False\n") + self._keepEmpties = False + + # Previously, snapshot labels used the ":" character was used as a + # separator character for datestamps. Windows filesystems such as + # CIFS and FAT choke on this character so now we use a user definable + # separator value, with a default value of "_" + # We need to check for both the old and new format when looking for + # snapshots. + self._separator = self._smf.get_separator() + self._prefix = "%s[:%s]" \ + % (autosnapsmf.SNAPLABELPREFIX, self._separator) + + # Rebuild pool list + self._zpools = [] + try: + for poolname in zfs.list_zpools(): + # Do not try to examine FAULTED pools + zpool = zfs.ZPool(poolname) + if zpool.health == "FAULTED": + util.debug("Ignoring faulted Zpool: %s\n" \ + % (zpool.name), \ + self.verbose) + else: + self._zpools.append(zpool) + util.debug(str(zpool), self.verbose) + except RuntimeError,message: + sys.stderr.write("Could not list Zpools\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate exception up to thread's run() method + raise RuntimeError,message + + + def _rebuild_schedules(self): + """ + Builds 2 lists of default and custom auto-snapshot SMF instances + """ + + self._last = {} + self._next = {} + self._keep = {} + + try: + _defaultSchedules = autosnapsmf.get_default_schedules() + _customSchedules = autosnapsmf.get_custom_schedules() + except RuntimeError,message: + self.exitCode = smf.SMF_EXIT_ERR_FATAL + raise RuntimeError, "Error reading SMF schedule instances\n" + \ + "Details:\n" + str(message) + else: + # Now set it in stone. + self._defaultSchedules = tuple(_defaultSchedules) + self._customSchedules = tuple(_customSchedules) + + # Build the combined schedule tuple from default + custom schedules + _defaultSchedules.extend(_customSchedules) + self._allSchedules = tuple(_defaultSchedules) + for schedule,i,p,keep in self._allSchedules: + self._last[schedule] = 0 + self._next[schedule] = 0 + self._keep[schedule] = keep + + def _update_schedules(self): + interval = 0 + idx = 1 # Used to index subsets for schedule overlap calculation + last = None + + for schedule,interval,period,keep in self._allSchedules: + # Shortcut if we've already processed this schedule and it's + # still up to date. Don't skip the default schedules though + # because overlap affects their scheduling + if [schedule,interval,period,keep] not in \ + self._defaultSchedules and \ + (self._next[schedule] > self._last[schedule]): + util.debug("Short circuiting %s recalculation" \ + % (schedule), \ + self.verbose) + continue + + # If we don't have an internal timestamp for the given schedule + # ask zfs for the last snapshot and get it's creation timestamp. + if self._last[schedule] == 0: + try: + snaps = self._datasets.list_snapshots("%s%s" % \ + (self._prefix, + schedule)) + except RuntimeError,message: + self.exitCode = smf.SMF_EXIT_ERR_FATAL + sys.stderr.write("Failed to list snapshots during schedule update\n") + #Propogate up to the thread's run() method + raise RuntimeError,message + + if len(snaps) > 0: + util.debug("Last %s snapshot was: %s" % \ + (schedule, snaps[-1][0]), \ + self.verbose) + self._last[schedule] = snaps[-1][1] + + last = self._last[schedule] + if interval != "months": # months is non-constant. See below. + util.debug("Recalculating %s schedule" % (schedule), \ + self.verbose) + try: + totalinterval = intervals[interval] * period + except KeyError: + self.exitCode = smf.SMF_EXIT_ERR_CONFIG + sys.stderr.write(schedule + \ + " schedule has invalid interval: " + \ + "'%s\'\n" % interval) + #Propogate up to thread's run() method + raise RuntimeError + if [schedule,interval,period,keep] in self._defaultSchedules: + # This is one of the default schedules so check for an + # overlap with one of the dominant shchedules. + for s,i,p,k in self._defaultSchedules[:idx]: + last = max(last, self._last[s]) + idx += 1 + + else: # interval == "months" + if self._next[schedule] > last: + util.debug("Short circuiting " + \ + schedule + \ + " recalculation", \ + self.verbose) + continue + util.debug("Recalculating %s schedule" % (schedule), \ + self.verbose) + snap_tm = time.gmtime(self._last[schedule]) + # Increment year if period >= than 1 calender year. + year = snap_tm.tm_year + year += period / 12 + period = period % 12 + + mon = (snap_tm.tm_mon + period) % 12 + # Result of 0 actually means december. + if mon == 0: + mon = 12 + # Account for period that spans calendar year boundary. + elif snap_tm.tm_mon + period > 12: + year += 1 + + d,dlastmon = calendar.monthrange(snap_tm.tm_year, snap_tm.tm_mon) + d,dnewmon = calendar.monthrange(year, mon) + mday = snap_tm.tm_mday + if dlastmon > dnewmon and snap_tm.tm_mday > dnewmon: + mday = dnewmon + + tm =(year, mon, mday, \ + snap_tm.tm_hour, snap_tm.tm_min, snap_tm.tm_sec, \ + 0, 0, -1) + newt = calendar.timegm(tm) + new_tm = time.gmtime(newt) + totalinterval = newt - self._last[schedule] + + self._next[schedule] = last + totalinterval + + def _next_due(self): + schedule = None + earliest = None + now = long(time.time()) + + for s,i,p,k in self._defaultSchedules: + due = self._next[s] + if due <= now: + #Default Schedule - so break out at the first + #schedule that is overdue. The subordinate schedules + #will re-adjust afterwards. + earliest,schedule = due,s + break + elif earliest != None: + if due < earliest: + earliest,schedule = due,s + else: #FIXME better optimisation with above condition + earliest,schedule = due,s + for s,i,p,k in self._customSchedules: + due = self._next[s] + if earliest != None: + if due < earliest: + earliest,schedule = due,s + else: #FIXME better optimisation with above condition + earliest,schedule = due,s + return earliest,schedule + + def _check_snapshots(self): + """ + Check the schedules and see what the required snapshot is. + Take one immediately on the first overdue snapshot required + """ + # Make sure a refresh() doesn't mess with the schedule while + # we're reading through it. + self._refreshLock.acquire() + next,schedule = self._next_due() + self._refreshLock.release() + now = long(time.time()) + while next != None and next <= now: + label = self._take_snapshots(schedule) + self._plugin.execute_plugins(schedule, label) + self._refreshLock.acquire() + self._update_schedules() + next,schedule = self._next_due(); + self._refreshLock.release() + dt = datetime.datetime.fromtimestamp(next) + util.debug("Next snapshot is %s due at: %s" % \ + (schedule, dt.isoformat()), \ + self.verbose) + return next + + def _take_snapshots(self, schedule): + # Set the time before taking snapshot to avoid clock skew due + # to time taken to complete snapshot. + tm = long(time.time()) + label = "%s%s%s-%s" % \ + (autosnapsmf.SNAPLABELPREFIX, self._separator, schedule, + datetime.datetime.now().strftime("%Y-%m-%d-%Hh%M")) + try: + self._datasets.create_auto_snapshot_set(label, tag=schedule) + except RuntimeError, message: + # Write an error message, set the exit code and pass it up the + # stack so the thread can terminate + sys.stderr.write("Failed to create snapshots for schedule: %s\n" \ + % (schedule)) + self.exitCode = smf.SMF_EXIT_MON_DEGRADE + raise RuntimeError,message + self._last[schedule] = tm; + self._perform_purge(schedule) + return label + + def _prune_snapshots(self, dataset, schedule): + """Cleans out zero sized snapshots, kind of cautiously""" + # Per schedule: We want to delete 0 sized + # snapshots but we need to keep at least one around (the most + # recent one) for each schedule so that that overlap is + # maintained from frequent -> hourly -> daily etc. + # Start off with the smallest interval schedule first and + # move up. This increases the amount of data retained where + # several snapshots are taken together like a frequent hourly + # and daily snapshot taken at 12:00am. If 3 snapshots are all + # identical and reference the same identical data they will all + # be initially reported as zero for used size. Deleting the + # daily first then the hourly would shift make the data referenced + # by all 3 snapshots unique to the frequent scheduled snapshot. + # This snapshot would probably be purged within an how ever and the + # data referenced by it would be gone for good. + # Doing it the other way however ensures that the data should + # remain accessible to the user for at least a week as long as + # the pool doesn't run low on available space before that. + + try: + snaps = dataset.list_snapshots("%s%s" % (self._prefix,schedule)) + # Clone the list because we want to remove items from it + # while iterating through it. + remainingsnaps = snaps[:] + except RuntimeError,message: + sys.stderr.write("Failed to list snapshots during snapshot cleanup\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + raise RuntimeError,message + + if (self._keepEmpties == False): + try: # remove the newest one from the list. + snaps.pop() + except IndexError: + pass + for snapname in snaps: + try: + snapshot = zfs.Snapshot(snapname) + except Exception,message: + sys.stderr.write(str(message)) + # Not fatal, just skip to the next snapshot + continue + + try: + if snapshot.get_used_size() == 0: + util.debug("Destroying zero sized: " + snapname, \ + self.verbose) + try: + snapshot.destroy() + except RuntimeError,message: + sys.stderr.write("Failed to destroy snapshot: " + + snapname + "\n") + self.exitCode = smf.SMF_EXIT_MON_DEGRADE + # Propogate exception so thread can exit + raise RuntimeError,message + remainingsnaps.remove(snapname) + except RuntimeError,message: + sys.stderr.write("Can not determine used size of: " + \ + snapname + "\n") + self.exitCode = smf.SMF_EXIT_MON_DEGRADE + #Propogate the exception to the thead run() method + raise RuntimeError,message + + # Deleting individual snapshots instead of recursive sets + # breaks the recursion chain and leaves child snapshots + # dangling so we need to take care of cleaning up the + # snapshots. + target = len(remainingsnaps) - self._keep[schedule] + counter = 0 + while counter < target: + util.debug("Destroy expired snapshot: " + \ + remainingsnaps[counter], + self.verbose) + try: + snapshot = zfs.Snapshot(remainingsnaps[counter]) + except Exception,message: + sys.stderr.write(str(message)) + # Not fatal, just skip to the next snapshot + counter += 1 + continue + try: + snapshot.destroy() + except RuntimeError,message: + sys.stderr.write("Failed to destroy snapshot: " + + snapshot.name + "\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate exception so thread can exit + raise RuntimeError,message + else: + counter += 1 + + def _perform_purge(self, schedule): + """Cautiously cleans out zero sized snapshots""" + # We need to avoid accidentally pruning auto snapshots received + # from one zpool to another. We ensure this by examining only + # snapshots whose parent fileystems and volumes are explicitly + # tagged to be snapshotted. + try: + for name in self._datasets.list_auto_snapshot_sets(schedule): + dataset = zfs.ReadWritableDataset(name) + self._prune_snapshots(dataset, schedule) + except RuntimeError,message: + sys.stderr.write("Error listing datasets during " + \ + "removal of expired snapshots\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate up to thread's run() method + raise RuntimeError,message + + def _needs_cleanup(self): + if self._remedialCleanup == False: + # Sys admin has explicitly instructed for remedial cleanups + # not to be performed. + return False + now = long(time.time()) + # Don't run checks any less than 15 minutes apart. + if self._cleanupLock.acquire(False) == False: + #Indicates that a cleanup is already running. + return False + # FIXME - Make the cleanup interval equal to the minimum snapshot interval + # if custom snapshot schedules are defined and enabled. + elif ((now - self._lastCleanupCheck) < (_MINUTE * 15)): + pass + else: + for zpool in self._zpools: + try: + if zpool.get_capacity() > self._warningLevel: + # Before getting into a panic, determine if the pool + # is one we actually take snapshots on, by checking + # for one of the "auto-snapshot: tags. Not + # super fast, but it only happens under exceptional + # circumstances of a zpool nearing it's capacity. + + for sched in self._allSchedules: + sets = zpool.list_auto_snapshot_sets(sched[0]) + if len(sets) > 0: + util.debug("%s needs a cleanup" \ + % zpool.name, \ + self.verbose) + self._cleanupLock.release() + return True + except RuntimeError, message: + sys.stderr.write("Error checking zpool capacity of: " + \ + zpool.name + "\n") + self._cleanupLock.release() + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate up to thread's run() mehod. + raise RuntimeError,message + self._lastCleanupCheck = long(time.time()) + self._cleanupLock.release() + return False + + def _perform_cleanup(self): + if self._cleanupLock.acquire(False) == False: + # Cleanup already running. Skip + return + self._destroyedsnaps = [] + for zpool in self._zpools: + try: + self._poolstatus[zpool.name] = 0 + capacity = zpool.get_capacity() + if capacity > self._warningLevel: + self._run_warning_cleanup(zpool) + self._poolstatus[zpool.name] = 1 + capacity = zpool.get_capacity() + if capacity > self._criticalLevel: + self._run_critical_cleanup(zpool) + self._poolstatus[zpool.name] = 2 + capacity = zpool.get_capacity() + if capacity > self._emergencyLevel: + self._run_emergency_cleanup(zpool) + self._poolstatus[zpool.name] = 3 + capacity = zpool.get_capacity() + if capacity > self._emergencyLevel: + self._run_emergency_cleanup(zpool) + self._poolstatus[zpool.name] = 4 + # This also catches exceptions thrown from _run__cleanup() + # and _run_cleanup() in methods called by _perform_cleanup() + except RuntimeError,message: + sys.stderr.write("Remedial space cleanup failed because " + \ + "of failure to determinecapacity of: " + \ + zpool.name + "\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + self._cleanupLock.release() + # Propogate up to thread's run() method. + raise RuntimeError,message + + # Bad - there's no more snapshots left and nothing + # left to delete. We don't disable the service since + # it will permit self recovery and snapshot + # retention when space becomes available on + # the pool (hopefully). + util.debug("%s pool status after cleanup:" \ + % zpool.name, \ + self.verbose) + util.debug(zpool, self.verbose) + util.debug("Cleanup completed. %d snapshots were destroyed" \ + % len(self._destroyedsnaps), \ + self.verbose) + # Avoid needless list iteration for non-debug mode + if self.verbose == True and len(self._destroyedsnaps) > 0: + for snap in self._destroyedsnaps: + sys.stderr.write("\t%s\n" % snap) + self._cleanupLock.release() + + def _run_warning_cleanup(self, zpool): + util.debug("Performing warning level cleanup on %s" % \ + zpool.name, \ + self.verbose) + self._run_cleanup(zpool, "daily", self._warningLevel) + if zpool.get_capacity() > self._warningLevel: + self._run_cleanup(zpool, "hourly", self._warningLevel) + + def _run_critical_cleanup(self, zpool): + util.debug("Performing critical level cleanup on %s" % \ + zpool.name, \ + self.verbose) + self._run_cleanup(zpool, "weekly", self._criticalLevel) + if zpool.get_capacity() > self._criticalLevel: + self._run_cleanup(zpool, "daily", self._criticalLevel) + if zpool.get_capacity() > self._criticalLevel: + self._run_cleanup(zpool, "hourly", self._criticalLevel) + + def _run_emergency_cleanup(self, zpool): + util.debug("Performing emergency level cleanup on %s" % \ + zpool.name, \ + self.verbose) + self._run_cleanup(zpool, "monthly", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "weekly", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "daily", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "hourly", self._emergencyLevel) + if zpool.get_capacity() > self._emergencyLevel: + self._run_cleanup(zpool, "frequent", self._emergencyLevel) + #Finally, as a last resort, delete custom scheduled snaphots + for schedule,i,p,k in self._customSchedules: + if zpool.get_capacity() < self._emergencyLevel: + break + else: + self._run_cleanup(zpool, schedule, self._emergencyLevel) + + def _run_cleanup(self, zpool, schedule, threshold): + clonedsnaps = [] + snapshots = [] + try: + clonedsnaps = self._datasets.list_cloned_snapshots() + except RuntimeError,message: + sys.stderr.write("Error (non-fatal) listing cloned snapshots" + + " while recovering pool capacity\n") + sys.stderr.write("Error details:\n" + \ + "--------BEGIN ERROR MESSAGE--------\n" + \ + str(message) + \ + "\n--------END ERROR MESSAGE--------\n") + + # Build a list of snapshots in the given schedule, that are not + # cloned, and sort the result in reverse chronological order. + try: + snapshots = [s for s,t in \ + zpool.list_snapshots("%s%s" \ + % (self._prefix,schedule)) \ + if not s in clonedsnaps] + snapshots.reverse() + except RuntimeError,message: + sys.stderr.write("Error listing snapshots" + + " while recovering pool capacity\n") + self.exitCode = smf.SMF_EXIT_ERR_FATAL + # Propogate the error up to the thread's run() method. + raise RuntimeError,message + + while zpool.get_capacity() > threshold: + if len(snapshots) == 0: + syslog.syslog(syslog.LOG_NOTICE, + "No more %s snapshots left" \ + % schedule) + return + + """This is not an exact science. Deleteing a zero sized + snapshot can have unpredictable results. For example a + pair of snapshots may share exclusive reference to a large + amount of data (eg. a large core file). The usage of both + snapshots will initially be seen to be 0 by zfs(1). Deleting + one of the snapshots will make the data become unique to the + single remaining snapshot that references it uniquely. The + remaining snapshot's size will then show up as non zero. So + deleting 0 sized snapshot is not as pointless as it might seem. + It also means we have to loop through this, each snapshot set + at a time and observe the before and after results. Perhaps + better way exists....""" + + # Start with the oldest first + snapname = snapshots.pop() + snapshot = zfs.Snapshot(snapname) + # It would be nicer, for performance purposes, to delete sets + # of snapshots recursively but this might destroy more data than + # absolutely necessary, plus the previous purging of zero sized + # snapshots can easily break the recursion chain between + # filesystems. + # On the positive side there should be fewer snapshots and they + # will mostly non-zero so we should get more effectiveness as a + # result of deleting snapshots since they should be nearly always + # non zero sized. + util.debug("Destroying %s" % snapname, self.verbose) + try: + snapshot.destroy() + except RuntimeError,message: + # Would be nice to be able to mark service as degraded here + # but it's better to try to continue on rather than to give + # up alltogether (SMF maintenance state) + sys.stderr.write("Warning: Cleanup failed to destroy: %s\n" % \ + (snapshot.name)) + sys.stderr.write("Details:\n%s\n" % (str(message))) + else: + self._destroyedsnaps.append(snapname) + # Give zfs some time to recalculate. + time.sleep(3) + + def _send_to_syslog(self): + for zpool in self._zpools: + status = self._poolstatus[zpool.name] + if status == 4: + syslog.syslog(syslog.LOG_EMERG, + "%s is over %d%% capacity. " \ + "All automatic snapshots were destroyed" \ + % (zpool.name, self._emergencyLevel)) + elif status == 3: + syslog.syslog(syslog.LOG_ALERT, + "%s exceeded %d%% capacity. " \ + "Automatic snapshots over 1 hour old were destroyed" \ + % (zpool.name, self._emergencyLevel)) + elif status == 2: + syslog.syslog(syslog.LOG_CRIT, + "%s exceeded %d%% capacity. " \ + "Weekly, hourly and daily automatic snapshots were destroyed" \ + % (zpool.name, self._criticalLevel)) + elif status == 1: + syslog.syslog(syslog.LOG_WARNING, + "%s exceeded %d%% capacity. " \ + "Hourly and daily automatic snapshots were destroyed" \ + % (zpool.name, self._warningLevel)) + + if len(self._destroyedsnaps) > 0: + syslog.syslog(syslog.LOG_NOTICE, + "%d automatic snapshots were destroyed" \ + % len(self._destroyedsnaps)) + + def _send_notification(self): + worstpool = None + worststatus = 0 + + for zpool in self._zpools: + status = self._poolstatus[zpool.name] + # >= to ensure that something should always be set. + if status >= worststatus: + worstpool = zpool.name + worststatus = status + + #FIXME make the various levels indexible + if worststatus == 4: + self._dbus.capacity_exceeded(worstpool, 4, self._emergencyLevel) + elif worststatus == 3: + self._dbus.capacity_exceeded(worstpool, 3, self._emergencyLevel) + elif worststatus == 2: + self._dbus.capacity_exceeded(worstpool, 2, self._criticalLevel) + elif worststatus == 1: + self._dbus.capacity_exceeded(worstpool, 1, self._warningLevel) + #elif: 0 everything is fine. Do nothing. + + +def monitor_threads(snapthread): + if snapthread.is_alive(): + return True + else: + sys.stderr.write("Snapshot monitor thread exited.\n") + if snapthread.exitCode == smf.SMF_EXIT_MON_DEGRADE: + # FIXME - it would be nicer to mark the service as degraded than + # go into maintenance state for some situations such as a + # particular snapshot schedule failing. + # But for now SMF does not implement this feature. But if/when it + # does it's better to use svcadm to put the # service into the + # correct state since the daemon shouldn't exit whentransitioning + # to a degraded state. + #sys.stderr.write("Placing service into maintenance state\n") + #subprocess.call(["/usr/sbin/svcadm", "mark", "maintenance", + # os.getenv("SMF_FMRI")]) + # SMF will take care of kill the daemon + sys.exit(smf.SMF_EXIT_ERR_FATAL) + return False + elif snapthread.exitCode == smf.SMF_EXIT_ERR_FATAL: + #sys.stderr.write("Placing service into maintenance state\n") + #subprocess.call(["/usr/sbin/svcadm", "mark", "maintenance", + # os.getenv("SMF_FMRI")]) + # SMF will take care of killing the daemon + sys.exit(smf.SMF_EXIT_ERR_FATAL) + return False + else: + sys.stderr.write("Snapshot monitor thread exited abnormally\n") + sys.stderr.write("Exit code: %d\n" % (snapthread.exitCode)) + #subprocess.call(["/usr/sbin/svcadm", "mark", "maintenance", + # os.getenv("SMF_FMRI")]) + sys.exit(smf.SMF_EXIT_ERR_FATAL) + return False + + +def child_sig_handler(signum, frame): + if signum == signal.SIGUSR1: + sys.exit(smf.SMF_EXIT_OK) + elif signum == signal.SIGCHLD: + sys.exit(smf.SMF_EXIT_ERR_FATAL) + elif signum == signal.SIGALRM: + sys.exit(smf.SMF_EXIT_ERR_FATAL) + +# Default daemon parameters. +# File mode creation mask of the daemon. +UMASK = 0 +# Default working directory for the daemon. +WORKDIR = "/" +# Default maximum for the number of available file descriptors. +MAXFD = 1024 + +def create_daemon(): + """ + Detach a process from the controlling terminal and run it in the + background as a daemon. + """ + #Catch signals that we might receive from child + signal.signal(signal.SIGCHLD, child_sig_handler) + signal.signal(signal.SIGUSR1, child_sig_handler) + signal.signal(signal.SIGALRM, child_sig_handler) + try: + pid = os.fork() + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if (pid == 0): + #Reset signals that we set to trap in parent + signal.signal(signal.SIGCHLD, signal.SIG_DFL) + signal.signal(signal.SIGUSR1, signal.SIG_DFL) + signal.signal(signal.SIGALRM, signal.SIG_DFL) + os.setsid() + os.chdir(WORKDIR) + os.umask(UMASK) + else: + #Wait for the child to give the OK or otherwise. + signal.pause() + + +def main(argv): + + # Check SMF invocation environment + if os.getenv("SMF_FMRI") == None or os.getenv("SMF_METHOD") != "start": + sys.stderr.write("Command line invocation of %s unsupported.\n" \ + % (sys.argv[0])) + sys.stderr.write("This command is intended for smf(5) invocation only.\n") + sys.exit(smf.SMF_EXIT_ERR_NOSMF) + + # Daemonise the service. + create_daemon() + + # The user security attributes checked are the following: + # Note that UID == 0 will match any profile search so + # no need to check it explicitly. + syslog.openlog("time-sliderd", 0, syslog.LOG_DAEMON) + rbacp = RBACprofile() + if rbacp.has_profile("ZFS File System Management"): + + gobject.threads_init() + + # Tell dbus to use the gobject mainloop for async ops + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + dbus.mainloop.glib.threads_init() + # Register a bus name with the system dbus daemon + systemBus = dbus.SystemBus() + name = dbus.service.BusName("org.opensolaris.TimeSlider", systemBus) + + # Create and start the snapshot manger. Takes care of + # auto snapshotting service and auto cleanup. + snapshot = SnapshotManager(systemBus) + snapshot.start() + gobject.timeout_add(2000, monitor_threads, snapshot) + + mainloop = gobject.MainLoop() + try: + mainloop.run() + except KeyboardInterrupt: + mainloop.quit() + sys.exit(smf.SMF_EXIT_OK) + else: + syslog.syslog(syslog.LOG_ERR, + "%s has insufficient privileges to run time-sliderd!" \ + % rbacp.name) + syslog.closelog() + sys.exit(smf.SMF_EXIT_ERR_PERM) + syslog.closelog() + sys.exit(smf.SMF_EXIT_OK) + diff --git a/usr/share/time-slider/lib/time_slider/timeslidersmf.py b/usr/share/time-slider/lib/time_slider/timeslidersmf.py new file mode 100755 index 0000000..6a50761 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/timeslidersmf.py @@ -0,0 +1,154 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import subprocess +import threading +import smf +import util + +#SMF EXIT CODES +SMF_EXIT_OK = 0 +SMF_EXIT_ERR_FATAL = 95 +SMF_EXIT_ERR_CONFIG = 96 +SMF_EXIT_MON_DEGRADE = 97 +SMF_EXIT_MON_OFFLINE = 98 +SMF_EXIT_ERR_NOSMF = 99 +SMF_EXIT_ERR_PERM = 100 +#SMF_EXIT_ERR_OTHER = non-zero + +cleanupTypes = ("warning", "critical", "emergency") + +SMFNAME = 'svc:/application/time-slider' +ZFSPROPGROUP = "zfs" +ZPOOLPROPGROUP = "zpool" +DAEMONPROPGROUP = "daemon" + +# Commonly used command paths +PFCMD = "/usr/bin/pfexec" +SVCSCMD = "/usr/bin/svcs" +SVCADMCMD = "/usr/sbin/svcadm" +SVCCFGCMD = "/usr/sbin/svccfg" +SVCPROPCMD = "/usr/bin/svcprop" + + +class TimeSliderSMF(smf.SMFInstance): + + def __init__(self, instanceName = SMFNAME): + smf.SMFInstance.__init__(self, instanceName) + self._cleanupLevels = {} + self._cleanupLevelsLock = threading.Lock() + + def get_keep_empties(self): + if self.get_prop(ZFSPROPGROUP, "keep-empties") == "true": + return True + else: + return False + + def is_custom_selection(self): + value = self.get_prop(ZFSPROPGROUP, "custom-selection") + if value == "true": + return True + else: + return False + + def get_separator(self): + result = self.get_prop(ZFSPROPGROUP, "sep") + if len(result) != 1: + raise ValueError("zfs/sep must be a single character length") + return result + + def get_remedial_cleanup(self): + value = self.get_prop(ZPOOLPROPGROUP, "remedial-cleanup") + if value == "false": + return False + else: + return True + + def get_cleanup_level(self, cleanupType): + if cleanupType not in cleanupTypes: + raise ValueError("\'%s\' is not a valid cleanup type" % \ + (cleanupType)) + self._cleanupLevelsLock.acquire() + value = self.get_prop(ZPOOLPROPGROUP, "%s-level" % (cleanupType)) + self._cleanupLevelsLock.release() + return int(value) + + def set_cleanup_level(self, cleanupType, level): + if cleanupType not in cleanupTypes: + raise ValueError("\'%s\' is not a valid cleanup type" % \ + (cleanupType)) + if level < 0: + raise ValueError("Cleanup level value can not not be negative") + if cleanupType == "warning" and \ + level > self.get_cleanup_level("critical"): + raise ValueError("Warning cleanup level value can not exceed " + \ + "critical cleanup level value") + elif cleanupType == "critical" and \ + level > self.get_cleanup_level("emergency"): + raise ValueError("Critical cleanup level value can not " + \ + "exceed emergency cleanup level value") + elif level > 100: # Emergency type value + raise ValueError("Cleanup level value can not exceed 100") + + self._cleanupLevelsLock.acquire() + propname = "%s-level" % (cleanupType) + self.set_integer_prop(ZPOOLPROPGROUP, propname, level) + self._cleanupLevels[cleanupType] = level + self._cleanupLevelsLock.release() + self.refresh_service() + + def set_custom_selection(self, value): + self.set_boolean_prop(ZFSPROPGROUP, "custom-selection", value) + self.refresh_service() + + def get_verbose(self): + value = self.get_prop(DAEMONPROPGROUP, "verbose") + if value == "true": + return True + else: + return False + + def __eq__(self, other): + if self.fs_name == other.fs_name and \ + self.interval == other.interval and \ + self.period == other.period: + return True + return False + + def __str__(self): + ret = "SMF Instance:\n" +\ + "\tName:\t\t\t%s\n" % (self.instanceName) +\ + "\tState:\t\t\t%s\n" % (self.svcstate) + \ + "\tVerbose:\t\t%s\n" % str(self.get_verbose()) + \ + "\tCustom Selction:\t%s\n" % str(self.is_custom_selection()) +\ + "\tKeep Empties:\t\t%s\n" % str(self.get_keep_empties()) +\ + "\tWarning Level:\t\t%d\n" % (self.get_cleanup_level("warning")) + \ + "\tCritical Level:\t\t%d\n" % (self.get_cleanup_level("critical")) + \ + "\tEmergency Level:\t%d\n" % (self.get_cleanup_level("emergency")) + \ + "\tSeparator Char:\t\t\'%s\'" % (self.get_separator()) + return ret + + +if __name__ == "__main__": + S = TimeSliderSMF('svc:/application/time-slider') + print S + diff --git a/usr/share/time-slider/lib/time_slider/tmp.py b/usr/share/time-slider/lib/time_slider/tmp.py new file mode 100755 index 0000000..4072f3b --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/tmp.py @@ -0,0 +1,23 @@ +#!/usr/bin/python2.6 +import os + +backupDirs = [] + +for root, dirs, files in os.walk("/ts-test/TIMESLIDER/nanmbp"): + if '.time-slider' in dirs: +# dirs.remove('.time-slider') + backupDirs.append(os.path.join(root, ".time-slider/rsync")) + print "root %s" % root + s1 = root.split ("/ts-test/TIMESLIDER/nanmbp/", 1) + print s1 + +for dirName in backupDirs: + print "dirName %s " % dirName + s1 = dirName.split ("/ts-test/TIMESLIDER/nanmbp/",1) + s2 = s1[1].split ("/.time-slider/rsync",1) + print s2[0] + os.chdir(dirName) + dirList = ["toto %s" % d for d in os.listdir(dirName) \ + if os.path.isdir(d) and + not os.path.islink(d)] + print dirList diff --git a/usr/share/time-slider/lib/time_slider/tmp.py~ b/usr/share/time-slider/lib/time_slider/tmp.py~ new file mode 100755 index 0000000..4072f3b --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/tmp.py~ @@ -0,0 +1,23 @@ +#!/usr/bin/python2.6 +import os + +backupDirs = [] + +for root, dirs, files in os.walk("/ts-test/TIMESLIDER/nanmbp"): + if '.time-slider' in dirs: +# dirs.remove('.time-slider') + backupDirs.append(os.path.join(root, ".time-slider/rsync")) + print "root %s" % root + s1 = root.split ("/ts-test/TIMESLIDER/nanmbp/", 1) + print s1 + +for dirName in backupDirs: + print "dirName %s " % dirName + s1 = dirName.split ("/ts-test/TIMESLIDER/nanmbp/",1) + s2 = s1[1].split ("/.time-slider/rsync",1) + print s2[0] + os.chdir(dirName) + dirList = ["toto %s" % d for d in os.listdir(dirName) \ + if os.path.isdir(d) and + not os.path.islink(d)] + print dirList diff --git a/usr/share/time-slider/lib/time_slider/tmp2.py b/usr/share/time-slider/lib/time_slider/tmp2.py new file mode 100755 index 0000000..d581a5b --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/tmp2.py @@ -0,0 +1,183 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import threading +import sys +import os +import time +import getopt +import locale +import shutil +import fcntl +from bisect import insort + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import glib + import gobject +except: + sys.exit(1) + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import rsyncsmf + + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from rbac import RBACprofile + +class RsyncBackup: + + def __init__(self, mountpoint, rsync_dir = None, fsname= None, snaplabel= None, creationtime= None): + + if rsync_dir == None: + self.__init_from_mp (mountpoint) + else: + self.rsync_dir = rsync_dir + self.mountpoint = mountpoint + self.fsname = fsname + self.snaplabel = snaplabel + + self.creationtime = creationtime + try: + tm = time.localtime(self.creationtime) + self.creationtime_str = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + self.creationtime_str = time.ctime(self.creationtime) + + def __init_from_mp (self, mountpoint): + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsync_dir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + self.mountpoint = mountpoint + + s1 = mountpoint.split ("%s/" % self.rsync_dir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + s3 = s2[1].split ('/',2) + self.fsname = s2[0] + self.snaplabel = s3[1] + self.creationtime = os.stat(mountpoint).st_mtime + + def __str__(self): + ret = "self.rsync_dir = %s\n \ + self.mountpoint = %s\n \ + self.fsname = %s\n \ + self.snaplabel = %s\n" % (self.rsync_dir, + self.mountpoint, self.fsname, + self.snaplabel) + return ret + + + def exists(self): + return os.path.exists(self.mountpoint) + + def destroy(self): + lockFileDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOCKSUFFIX) + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + lockFile = os.path.join(lockFileDir, self.snaplabel + ".lock") + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + raise RuntimeError, \ + "couldn't delete %s, already used by another process" % self.mountpoint + return + + trashDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX) + if not os.path.exists(trashDir): + os.makedirs(trashDir, 0755) + + backupTrashDir = os.path.join (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX, + self.snaplabel) + + # move then delete + os.rename (self.mountpoint, backupTrashDir) + shutil.rmtree (backupTrashDir) + + log = "%s/%s/%s/%s/%s.log" % (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCDIRSUFFIX, + ".partial", + self.snaplabel) + if os.path.exists (log): + os.unlink (log) + + lockFp.close() + os.unlink(lockFile) + + +backupDirs = [] +for root, dirs, files in os.walk(rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)).get_target_dir ()): + if '.time-slider' in dirs: + dirs.remove('.time-slider') + backupDir = os.path.join(root, rsyncsmf.RSYNCDIRSUFFIX) + if os.path.exists(backupDir): + insort(backupDirs, os.path.abspath(backupDir)) + + +print backupDirs + + diff --git a/usr/share/time-slider/lib/time_slider/tmp2.py~ b/usr/share/time-slider/lib/time_slider/tmp2.py~ new file mode 100755 index 0000000..84a3a59 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/tmp2.py~ @@ -0,0 +1,183 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import threading +import sys +import os +import time +import getopt +import locale +import shutil +import fcntl +import bisect import insort + +try: + import pygtk + pygtk.require("2.4") +except: + pass +try: + import gtk + import gtk.glade + gtk.gdk.threads_init() +except: + sys.exit(1) +try: + import glib + import gobject +except: + sys.exit(1) + +from os.path import abspath, dirname, join, pardir +sys.path.insert(0, join(dirname(__file__), pardir, "plugin")) +import plugin +sys.path.insert(0, join(dirname(__file__), pardir, "plugin", "rsync")) +import rsyncsmf + + +# here we define the path constants so that other modules can use it. +# this allows us to get access to the shared files without having to +# know the actual location, we just use the location of the current +# file and use paths relative to that. +SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir)) +LOCALE_PATH = os.path.join('/usr', 'share', 'locale') +RESOURCE_PATH = os.path.join(SHARED_FILES, 'res') + +# the name of the gettext domain. because we have our translation files +# not in a global folder this doesn't really matter, setting it to the +# application name is a good idea tough. +GETTEXT_DOMAIN = 'time-slider' + +# set up the glade gettext system and locales +gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH) +gtk.glade.textdomain(GETTEXT_DOMAIN) + +import zfs +from rbac import RBACprofile + +class RsyncBackup: + + def __init__(self, mountpoint, rsync_dir = None, fsname= None, snaplabel= None, creationtime= None): + + if rsync_dir == None: + self.__init_from_mp (mountpoint) + else: + self.rsync_dir = rsync_dir + self.mountpoint = mountpoint + self.fsname = fsname + self.snaplabel = snaplabel + + self.creationtime = creationtime + try: + tm = time.localtime(self.creationtime) + self.creationtime_str = unicode(time.strftime ("%c", tm), + locale.getpreferredencoding()).encode('utf-8') + except: + self.creationtime_str = time.ctime(self.creationtime) + + def __init_from_mp (self, mountpoint): + self.rsyncsmf = rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)) + rsyncBaseDir = self.rsyncsmf.get_target_dir() + sys,nodeName,rel,ver,arch = os.uname() + self.rsync_dir = os.path.join(rsyncBaseDir, + rsyncsmf.RSYNCDIRPREFIX, + nodeName) + self.mountpoint = mountpoint + + s1 = mountpoint.split ("%s/" % self.rsync_dir, 1) + s2 = s1[1].split ("/%s" % rsyncsmf.RSYNCDIRSUFFIX, 1) + s3 = s2[1].split ('/',2) + self.fsname = s2[0] + self.snaplabel = s3[1] + self.creationtime = os.stat(mountpoint).st_mtime + + def __str__(self): + ret = "self.rsync_dir = %s\n \ + self.mountpoint = %s\n \ + self.fsname = %s\n \ + self.snaplabel = %s\n" % (self.rsync_dir, + self.mountpoint, self.fsname, + self.snaplabel) + return ret + + + def exists(self): + return os.path.exists(self.mountpoint) + + def destroy(self): + lockFileDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCLOCKSUFFIX) + + if not os.path.exists(lockFileDir): + os.makedirs(lockFileDir, 0755) + + lockFile = os.path.join(lockFileDir, self.snaplabel + ".lock") + try: + lockFp = open(lockFile, 'w') + fcntl.flock(lockFp, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + raise RuntimeError, \ + "couldn't delete %s, already used by another process" % self.mountpoint + return + + trashDir = os.path.join(self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX) + if not os.path.exists(trashDir): + os.makedirs(trashDir, 0755) + + backupTrashDir = os.path.join (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCTRASHSUFFIX, + self.snaplabel) + + # move then delete + os.rename (self.mountpoint, backupTrashDir) + shutil.rmtree (backupTrashDir) + + log = "%s/%s/%s/%s/%s.log" % (self.rsync_dir, + self.fsname, + rsyncsmf.RSYNCDIRSUFFIX, + ".partial", + self.snaplabel) + if os.path.exists (log): + os.unlink (log) + + lockFp.close() + os.unlink(lockFile) + + +backupDirs = [] +for root, dirs, files in os.walk(rsyncsmf.RsyncSMF("%s:rsync" %(plugin.PLUGINBASEFMRI)).get_target_dir ()): + if '.time-slider' in dirs: + dirs.remove('.time-slider') + backupDir = os.path.join(root, rsyncsmf.RSYNCDIRSUFFIX) + if os.path.exists(backupDir): + insort(backupDirs, os.path.abspath(backupDir)) + + +print backupDirs + + diff --git a/usr/share/time-slider/lib/time_slider/util.py b/usr/share/time-slider/lib/time_slider/util.py new file mode 100644 index 0000000..e48326c --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/util.py @@ -0,0 +1,134 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import subprocess +import sys +import syslog +import statvfs +import math +import gio + +def run_command(command, raise_on_try=True): + """ + Wrapper function around subprocess.Popen + Returns a tuple of standard out and stander error. + Throws a RunTimeError if the command failed to execute or + if the command returns a non-zero exit status. + """ + try: + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (command, str(message)) + if err != 0 and raise_on_try: + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(command), err, errdata) + return outdata,errdata + +def debug(message, verbose): + """ + Prints message out to standard error and syslog if + verbose = True. + Note that the caller needs to first establish a syslog + context using syslog.openlog() + """ + if verbose: + syslog.syslog(syslog.LOG_NOTICE, message + '\n') + sys.stderr.write(message + '\n') + +def log_error(loglevel, message): + """ + Trivial syslog wrapper that also outputs to stderr + Requires caller to have first opened a syslog session + using syslog.openlog() + """ + syslog.syslog(loglevel, message + '\n') + sys.stderr.write(message + '\n') + +def get_filesystem_capacity(path): + """Returns filesystem space usage of path as an integer percentage of + the entire capacity of path. + """ + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + + unavailBlocks = f[statvfs.F_BLOCKS] - f[statvfs.F_BAVAIL] + capacity = int(math.ceil(100 * (unavailBlocks / float(f[statvfs.F_BLOCKS])))) + + return capacity + +def get_available_size(path): + """Returns the available space in bytes under path""" + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + free = long(f[statvfs.F_BAVAIL] * f[statvfs.F_FRSIZE]) + + return free + +def get_used_size(path): + """Returns the used space in bytes of fileystem associated + with path""" + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + + unavailBlocks = f[statvfs.F_BLOCKS] - f[statvfs.F_BAVAIL] + used = long(unavailBlocks * f[statvfs.F_FRSIZE]) + + return used + +def get_total_size(path): + """Returns the total storage space in bytes of fileystem + associated with path""" + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + total = long(f[statvfs.F_BLOCKS] * f[statvfs.F_FRSIZE]) + + return total + +def path_to_volume(path): + """ + Tries to map a given path name to a gio Volume and + returns the gio.Volume object the enclosing + volume. + If it fails to find an enclosing volume it returns + None + """ + gFile = gio.File(path) + try: + mount = gFile.find_enclosing_mount() + except gio.Error: + return None + else: + if mount != None: + volume = mount.get_volume() + return volume + return None diff --git a/usr/share/time-slider/lib/time_slider/util.py~ b/usr/share/time-slider/lib/time_slider/util.py~ new file mode 100644 index 0000000..e48326c --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/util.py~ @@ -0,0 +1,134 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import os +import subprocess +import sys +import syslog +import statvfs +import math +import gio + +def run_command(command, raise_on_try=True): + """ + Wrapper function around subprocess.Popen + Returns a tuple of standard out and stander error. + Throws a RunTimeError if the command failed to execute or + if the command returns a non-zero exit status. + """ + try: + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (command, str(message)) + if err != 0 and raise_on_try: + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(command), err, errdata) + return outdata,errdata + +def debug(message, verbose): + """ + Prints message out to standard error and syslog if + verbose = True. + Note that the caller needs to first establish a syslog + context using syslog.openlog() + """ + if verbose: + syslog.syslog(syslog.LOG_NOTICE, message + '\n') + sys.stderr.write(message + '\n') + +def log_error(loglevel, message): + """ + Trivial syslog wrapper that also outputs to stderr + Requires caller to have first opened a syslog session + using syslog.openlog() + """ + syslog.syslog(loglevel, message + '\n') + sys.stderr.write(message + '\n') + +def get_filesystem_capacity(path): + """Returns filesystem space usage of path as an integer percentage of + the entire capacity of path. + """ + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + + unavailBlocks = f[statvfs.F_BLOCKS] - f[statvfs.F_BAVAIL] + capacity = int(math.ceil(100 * (unavailBlocks / float(f[statvfs.F_BLOCKS])))) + + return capacity + +def get_available_size(path): + """Returns the available space in bytes under path""" + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + free = long(f[statvfs.F_BAVAIL] * f[statvfs.F_FRSIZE]) + + return free + +def get_used_size(path): + """Returns the used space in bytes of fileystem associated + with path""" + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + + unavailBlocks = f[statvfs.F_BLOCKS] - f[statvfs.F_BAVAIL] + used = long(unavailBlocks * f[statvfs.F_FRSIZE]) + + return used + +def get_total_size(path): + """Returns the total storage space in bytes of fileystem + associated with path""" + if not os.path.exists(path): + raise ValueError("%s is a non-existent path" % path) + f = os.statvfs(path) + total = long(f[statvfs.F_BLOCKS] * f[statvfs.F_FRSIZE]) + + return total + +def path_to_volume(path): + """ + Tries to map a given path name to a gio Volume and + returns the gio.Volume object the enclosing + volume. + If it fails to find an enclosing volume it returns + None + """ + gFile = gio.File(path) + try: + mount = gFile.find_enclosing_mount() + except gio.Error: + return None + else: + if mount != None: + volume = mount.get_volume() + return volume + return None diff --git a/usr/share/time-slider/lib/time_slider/zfs.py b/usr/share/time-slider/lib/time_slider/zfs.py new file mode 100755 index 0000000..b35e58d --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/zfs.py @@ -0,0 +1,1049 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import subprocess +import re +import threading +from bisect import insort, bisect_left, bisect_right + +import util + +BYTESPERMB = 1048576 + +# Commonly used command paths +PFCMD = "/usr/bin/pfexec" +ZFSCMD = "/usr/sbin/zfs" +ZPOOLCMD = "/usr/sbin/zpool" + + +class Datasets(Exception): + """ + Container class for all zfs datasets. Maintains a centralised + list of datasets (generated on demand) and accessor methods. + Also allows clients to notify when a refresh might be necessary. + """ + # Class wide instead of per-instance in order to avoid duplication + filesystems = None + volumes = None + snapshots = None + + # Mutex locks to prevent concurrent writes to above class wide + # dataset lists. + _filesystemslock = threading.Lock() + _volumeslock = threading.Lock() + snapshotslock = threading.Lock() + + def create_auto_snapshot_set(self, label, tag = None): + """ + Create a complete set of snapshots as if this were + for a standard zfs-auto-snapshot operation. + + Keyword arguments: + label: + A label to apply to the snapshot name. Cannot be None. + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" for will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + everything = [] + included = [] + excluded = [] + single = [] + recursive = [] + finalrecursive = [] + + # Get auto-snap property in two passes. First with the schedule + # specific tag override value, then with the general property value + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name,com.sun:auto-snapshot", "-s", "name"] + if tag: + overrideprop = "com.sun:auto-snapshot:" + tag + scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name," + overrideprop, "-s", "name"] + outdata,errdata = util.run_command(scmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + # Skip over unset values. + if line[1] == "-": + continue + # Add to everything list. This is used later + # for identifying parents/children of a given + # filesystem or volume. + everything.append(line[0]) + if line[1] == "true": + included.append(line[0]) + elif line[1] == "false": + excluded.append(line[0]) + # Now use the general property. If no value + # was set in the first pass, we set it here. + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + idx = bisect_right(everything, line[0]) + if len(everything) == 0 or \ + everything[idx-1] != line[0]: + # Dataset is neither included nor excluded so far + if line[1] == "-": + continue + everything.insert(idx, line[0]) + if line[1] == "true": + included.insert(0, line[0]) + elif line[1] == "false": + excluded.append(line[0]) + + # Now figure out what can be recursively snapshotted and what + # must be singly snapshotted. Single snapshot restrictions apply + # to those datasets who have a child in the excluded list. + # 'included' is sorted in reverse alphabetical order. + for datasetname in included: + excludedchild = False + idx = bisect_right(everything, datasetname) + children = [name for name in everything[idx:] if \ + name.find(datasetname) == 0] + for child in children: + idx = bisect_left(excluded, child) + if idx < len(excluded) and excluded[idx] == child: + excludedchild = True + single.append(datasetname) + break + if excludedchild == False: + # We want recursive list sorted in alphabetical order + # so insert instead of append to the list. + recursive.insert(0, datasetname) + + for datasetname in recursive: + parts = datasetname.rsplit('/', 1) + parent = parts[0] + if parent == datasetname: + # Root filesystem of the Zpool, so + # this can't be inherited and must be + # set locally. + finalrecursive.append(datasetname) + continue + idx = bisect_right(recursive, parent) + if len(recursive) > 0 and \ + recursive[idx-1] == parent: + # Parent already marked for recursive snapshot: so skip + continue + else: + finalrecursive.append(datasetname) + + for name in finalrecursive: + dataset = ReadWritableDataset(name) + dataset.create_snapshot(label, True) + for name in single: + dataset = ReadWritableDataset(name) + dataset.create_snapshot(label, False) + + def list_auto_snapshot_sets(self, tag = None): + """ + Returns a list of zfs filesystems and volumes tagged with + the "com.sun:auto-snapshot" property set to "true", either + set locally or inherited. Snapshots are excluded from the + returned result. + + Keyword Arguments: + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + #Get auto-snap property in two passes. First with the global + #value, then overriding with the label/schedule specific value + + included = [] + excluded = [] + + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name,com.sun:auto-snapshot", "-s", "name"] + if tag: + overrideprop = "com.sun:auto-snapshot:" + tag + scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name," + overrideprop, "-s", "name"] + outdata,errdata = util.run_command(scmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + if line[1] == "true": + included.append(line[0]) + elif line[1] == "false": + excluded.append(line[0]) + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + # Only set values that aren't already set. Don't override + try: + included.index(line[0]) + continue + except ValueError: + try: + excluded.index(line[0]) + continue + except ValueError: + # Dataset is not listed in either list. + if line[1] == "true": + included.append(line[0]) + return included + + def list_filesystems(self, pattern = None): + """ + List pattern matching filesystems sorted by name. + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + filesystems = [] + # Need to first ensure no other thread is trying to + # build this list at the same time. + Datasets._filesystemslock.acquire() + if Datasets.filesystems == None: + Datasets.filesystems = [] + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem", \ + "-o", "name,mountpoint", "-s", "name"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets._filesystemslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for line in outdata.rstrip().split('\n'): + line = line.rstrip().split() + Datasets.filesystems.append([line[0], line[1]]) + Datasets._filesystemslock.release() + + if pattern == None: + filesystems = Datasets.filesystems[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*%s.*" % pattern + patternobj = re.compile(regexpattern) + + for fsname,fsmountpoint in Datasets.filesystems: + patternmatchobj = re.match(patternobj, fsname) + if patternmatchobj != None: + filesystems.append(fsname, fsmountpoint) + return filesystems + + def list_volumes(self, pattern = None): + """ + List pattern matching volumes sorted by name. + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + volumes = [] + Datasets._volumeslock.acquire() + if Datasets.volumes == None: + Datasets.volumes = [] + cmd = [ZFSCMD, "list", "-H", "-t", "volume", \ + "-o", "name", "-s", "name"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets._volumeslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for line in outdata.rstrip().split('\n'): + Datasets.volumes.append(line.rstrip()) + Datasets._volumeslock.release() + + if pattern == None: + volumes = Datasets.volumes[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*%s.*" % pattern + patternobj = re.compile(regexpattern) + + for volname in Datasets.volumes: + patternmatchobj = re.match(patternobj, volname) + if patternmatchobj != None: + volumes.append(volname) + return volumes + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + snapshots = [] + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + Datasets.snapshots = [] + snaps = [] + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value,name", "creation"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err= p.wait() + except OSError, message: + Datasets.snapshotslock.release() + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets.snapshotslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for dataset in outdata.rstrip().split('\n'): + if re.search("@", dataset): + insort(snaps, dataset.split()) + for snap in snaps: + Datasets.snapshots.append([snap[1], long(snap[0])]) + if pattern == None: + snapshots = Datasets.snapshots[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*@.*%s" % pattern + patternobj = re.compile(regexpattern) + + for snapname,snaptime in Datasets.snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append([snapname, snaptime]) + Datasets.snapshotslock.release() + return snapshots + + def list_cloned_snapshots(self): + """ + Returns a list of snapshots that have cloned filesystems + dependent on them. + Snapshots with cloned filesystems can not be destroyed + unless dependent cloned filesystems are first destroyed. + """ + cmd = [ZFSCMD, "list", "-H", "-o", "origin"] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + details = line.rstrip() + if details != "-": + try: + result.index(details) + except ValueError: + result.append(details) + return result + + def list_held_snapshots(self): + """ + Returns a list of snapshots that have a "userrefs" + property value of greater than 0. Resul list is + sorted in order of creation time. Oldest listed first. + """ + cmd = [ZFSCMD, "list", "-H", + "-t", "snapshot", + "-s", "creation", + "-o", "userrefs,name"] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + details = line.split() + if details[0] != "0": + result.append(details[1]) + return result + + def refresh_snapshots(self): + """ + Should be called when snapshots have been created or deleted + and a rescan should be performed. Rescan gets deferred until + next invocation of zfs.Dataset.list_snapshots() + """ + # FIXME in future. + # This is a little sub-optimal because we should be able to modify + # the snapshot list in place in some situations and regenerate the + # snapshot list without calling out to zfs(1m). But on the + # pro side, we will pick up any new snapshots since the last + # scan that we would be otherwise unaware of. + Datasets.snapshotslock.acquire() + Datasets.snapshots = None + Datasets.snapshotslock.release() + + +class ZPool: + """ + Base class for ZFS storage pool objects + """ + def __init__(self, name): + self.name = name + self.health = self.__get_health() + self.__datasets = Datasets() + self.__filesystems = None + self.__volumes = None + self.__snapshots = None + + def __get_health(self): + """ + Returns pool health status: 'ONLINE', 'DEGRADED' or 'FAULTED' + """ + cmd = [ZPOOLCMD, "list", "-H", "-o", "health", self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + return result + + def get_capacity(self): + """ + Returns the percentage of total pool storage in use. + Calculated based on the "used" and "available" properties + of the pool's top-level filesystem because the values account + for reservations and quotas of children in their calculations, + giving a more practical indication of how much capacity is used + up on the pool. + """ + if self.health == "FAULTED": + raise ZPoolFaultedError("Can not determine capacity of zpool: %s" \ + "because it is in a FAULTED state" \ + % (self.name)) + + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", \ + "used,available", self.name] + outdata,errdata = util.run_command(cmd) + _used,_available = outdata.rstrip().split('\n') + used = float(_used) + available = float(_available) + return 100.0 * used/(used + available) + + def get_available_size(self): + """ + How much unused space is available for use on this Zpool. + Answer in bytes. + """ + # zpool(1) doesn't report available space in + # units suitable for calulations but zfs(1) + # can so use it to find the value for the + # filesystem matching the pool. + # The root filesystem of the pool is simply + # the pool name. + poolfs = Filesystem(self.name) + avail = poolfs.get_available_size() + return avail + + def get_used_size(self): + """ + How much space is in use on this Zpool. + Answer in bytes + """ + # Same as ZPool.get_available_size(): zpool(1) + # doesn't generate suitable out put so use + # zfs(1) on the toplevel filesystem + if self.health == "FAULTED": + raise ZPoolFaultedError("Can not determine used size of zpool: %s" \ + "because it is in a FAULTED state" \ + % (self.name)) + poolfs = Filesystem(self.name) + used = poolfs.get_used_size() + return used + + def list_filesystems(self): + """ + Return a list of filesystems on this Zpool. + List is sorted by name. + """ + if self.__filesystems == None: + result = [] + # Provides pre-sorted filesystem list + for fsname,fsmountpoint in self.__datasets.list_filesystems(): + if re.match(self.name, fsname): + result.append([fsname, fsmountpoint]) + self.__filesystems = result + return self.__filesystems + + def list_volumes(self): + """ + Return a list of volumes (zvol) on this Zpool + List is sorted by name + """ + if self.__volumes == None: + result = [] + regexpattern = "^%s" % self.name + patternobj = re.compile(regexpattern) + for volname in self.__datasets.list_volumes(): + patternmatchobj = re.match(patternobj, volname) + if patternmatchobj != None: + result.append(volname) + result.sort() + self.__volumes = result + return self.__volumes + + def list_auto_snapshot_sets(self, tag = None): + """ + Returns a list of zfs filesystems and volumes tagged with + the "com.sun:auto-snapshot" property set to "true", either + set locally or inherited. Snapshots are excluded from the + returned result. Results are not sorted. + + Keyword Arguments: + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + result = [] + allsets = self.__datasets.list_auto_snapshot_sets(tag) + if len(allsets) == 0: + return result + + regexpattern = "^%s" % self.name + patternobj = re.compile(regexpattern) + for datasetname in allsets: + patternmatchobj = re.match(patternobj, datasetname) + if patternmatchobj != None: + result.append(datasetname) + return result + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + # If there isn't a list of snapshots for this dataset + # already, create it now and store it in order to save + # time later for potential future invocations. + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + self.__snapshots = None + Datasets.snapshotslock.release() + if self.__snapshots == None: + result = [] + regexpattern = "^%s.*@" % self.name + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__datasets.list_snapshots(): + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + result.append([snapname, snaptime]) + # Results already sorted by creation time + self.__snapshots = result + if pattern == None: + return self.__snapshots + else: + snapshots = [] + regexpattern = "^%s.*@.*%s" % (self.name, pattern) + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append([snapname, snaptime]) + return snapshots + + def __str__(self): + return_string = "ZPool name: " + self.name + return_string = return_string + "\n\tHealth: " + self.health + try: + return_string = return_string + \ + "\n\tUsed: " + \ + str(self.get_used_size()/BYTESPERMB) + "Mb" + return_string = return_string + \ + "\n\tAvailable: " + \ + str(self.get_available_size()/BYTESPERMB) + "Mb" + return_string = return_string + \ + "\n\tCapacity: " + \ + str(self.get_capacity()) + "%" + except ZPoolFaultedError: + pass + return return_string + + +class ReadableDataset: + """ + Base class for Filesystem, Volume and Snapshot classes + Provides methods for read only operations common to all. + """ + def __init__(self, name, creation = None): + self.name = name + self.__creationTime = creation + self.datasets = Datasets() + + def __str__(self): + return_string = "ReadableDataset name: " + self.name + "\n" + return return_string + + def get_creation_time(self): + if self.__creationTime == None: + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "creation", + self.name] + outdata,errdata = util.run_command(cmd) + self.__creationTime = long(outdata.rstrip()) + return self.__creationTime + + def exists(self): + """ + Returns True if the dataset is still existent on the system. + False otherwise + """ + # Test existance of the dataset by checking the output of a + # simple zfs get command on the snapshot + cmd = [ZFSCMD, "get", "-H", "-o", "name", "type", self.name] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (command, str(message)) + if err != 0: + # Doesn't exist + return False + + result = outdata.rstrip() + if result == self.name: + return True + else: + return False + + def get_used_size(self): + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "used", self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def get_user_property(self, prop, local=False): + if local == True: + cmd = [ZFSCMD, "get", "-s", "local", "-H", "-o", "value", prop, self.name] + else: + cmd = [ZFSCMD, "get", "-H", "-o", "value", prop, self.name] + outdata,errdata = util.run_command(cmd) + return outdata.rstrip() + + def set_user_property(self, prop, value): + cmd = [PFCMD, ZFSCMD, "set", "%s=%s" % (prop, value), self.name] + outdata,errdata = util.run_command(cmd) + + def unset_user_property(self, prop): + cmd = [PFCMD, ZFSCMD, "inherit", prop, self.name] + outdata,errdata = util.run_command(cmd) + +class Snapshot(ReadableDataset): + """ + ZFS Snapshot object class. + Provides information and operations specfic to ZFS snapshots + """ + def __init__(self, name, creation = None): + """ + Keyword arguments: + name -- Name of the ZFS snapshot + creation -- Creation time of the snapshot if known (Default None) + """ + ReadableDataset.__init__(self, name, creation) + self.fsname, self.snaplabel = self.__split_snapshot_name() + self.poolname = self.__get_pool_name() + + def __get_pool_name(self): + name = self.fsname.split("/", 1) + return name[0] + + def __split_snapshot_name(self): + name = self.name.split("@", 1) + # Make sure this is really a snapshot and not a + # filesystem otherwise a filesystem could get + # destroyed instead of a snapshot. That would be + # really really bad. + if name[0] == self.name: + raise SnapshotError("\'%s\' is not a valid snapshot name" \ + % (self.name)) + return name[0],name[1] + + def get_referenced_size(self): + """ + How much unique storage space is used by this snapshot. + Answer in bytes + """ + cmd = [ZFSCMD, "get", "-H", "-p", \ + "-o", "value", "referenced", \ + self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def list_children(self): + """Returns a recursive list of child snapshots of this snapshot""" + cmd = [ZFSCMD, + "list", "-t", "snapshot", "-H", "-r", "-o", "name", + self.fsname] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if re.search("@%s" % (self.snaplabel), line) and \ + line != self.name: + result.append(line) + return result + + def has_clones(self): + """Returns True if the snapshot has any dependent clones""" + cmd = [ZFSCMD, "list", "-H", "-o", "origin,name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + details = line.rstrip().split() + if details[0] == self.name and \ + details[1] != '-': + return True + return False + + def destroy(self, deferred=True): + """ + Permanently remove this snapshot from the filesystem + Performs deferred destruction by default. + """ + # Be sure it genuninely exists before trying to destroy it + if self.exists() == False: + return + if deferred == False: + cmd = [PFCMD, ZFSCMD, "destroy", self.name] + else: + cmd = [PFCMD, ZFSCMD, "destroy", "-d", self.name] + + outdata,errdata = util.run_command(cmd) + # Clear the global snapshot cache so that a rescan will be + # triggered on the next call to Datasets.list_snapshots() + self.datasets.refresh_snapshots() + + def hold(self, tag): + """ + Place a hold on the snapshot with the specified "tag" string. + """ + # FIXME - fails if hold is already held + # Be sure it genuninely exists before trying to place a hold + if self.exists() == False: + return + + cmd = [PFCMD, ZFSCMD, "hold", tag, self.name] + outdata,errdata = util.run_command(cmd) + + def holds(self): + """ + Returns a list of user hold tags for this snapshot + """ + cmd = [ZFSCMD, "holds", self.name] + results = [] + outdata,errdata = util.run_command(cmd) + + for line in outdata.rstrip().split('\n'): + if len(line) == 0: + continue + # The first line heading columns are NAME TAG TIMESTAMP + # Filter that line out. + line = line.split() + if (line[0] != "NAME" and line[1] != "TAG"): + results.append(line[1]) + return results + + def release(self, tag,): + """ + Release the hold on the snapshot with the specified "tag" string. + """ + # FIXME raises exception if no hold exists. + # Be sure it genuninely exists before trying to destroy it + if self.exists() == False: + return + + cmd = [PFCMD, ZFSCMD, "release", tag, self.name] + + outdata,errdata = util.run_command(cmd) + # Releasing the snapshot might cause it get automatically + # deleted by zfs. + # Clear the global snapshot cache so that a rescan will be + # triggered on the next call to Datasets.list_snapshots() + self.datasets.refresh_snapshots() + + + def __str__(self): + return_string = "Snapshot name: " + self.name + return_string = return_string + "\n\tCreation time: " \ + + str(self.get_creation_time()) + return_string = return_string + "\n\tUsed Size: " \ + + str(self.get_used_size()) + return_string = return_string + "\n\tReferenced Size: " \ + + str(self.get_referenced_size()) + return return_string + + +class ReadWritableDataset(ReadableDataset): + """ + Base class for ZFS filesystems and volumes. + Provides methods for operations and properties + common to both filesystems and volumes. + """ + def __init__(self, name, creation = None): + ReadableDataset.__init__(self, name, creation) + self.__snapshots = None + + def __str__(self): + return_string = "ReadWritableDataset name: " + self.name + "\n" + return return_string + + def get_auto_snap(self, schedule = None): + if schedule: + cmd = [ZFSCMD, "get", "-H", "-o", "value", \ + "com.sun:auto-snapshot", self.name] + cmd = [ZFSCMD, "get", "-H", "-o", "value", \ + "com.sun:auto-snapshot", self.name] + outdata,errdata = util.run_command(cmd) + if outdata.rstrip() == "true": + return True + else: + return False + + def get_available_size(self): + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "available", \ + self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def create_snapshot(self, snaplabel, recursive = False): + """ + Create a snapshot for the ReadWritable dataset using the supplied + snapshot label. + + Keyword Arguments: + snaplabel: + A string to use as the snapshot label. + The bit that comes after the "@" part of the snapshot + name. + recursive: + Recursively snapshot childfren of this dataset. + Default = False + """ + cmd = [PFCMD, ZFSCMD, "snapshot"] + if recursive == True: + cmd.append("-r") + cmd.append("%s@%s" % (self.name, snaplabel)) + outdata,errdata = util.run_command(cmd, False) + if errdata: + print errdata + self.datasets.refresh_snapshots() + + def list_children(self): + + # Note, if more dataset types ever come around they will + # need to be added to the filsystem,volume args below. + # Not for the forseeable future though. + cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem,volume", + "-o", "name", self.name] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if line.rstrip() != self.name: + result.append(line.rstrip()) + return result + + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + # If there isn't a list of snapshots for this dataset + # already, create it now and store it in order to save + # time later for potential future invocations. + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + self.__snapshots = None + Datasets.snapshotslock.release() + if self.__snapshots == None: + result = [] + regexpattern = "^%s@" % self.name + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.datasets.list_snapshots(): + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + result.append([snapname, snaptime]) + # Results already sorted by creation time + self.__snapshots = result + if pattern == None: + return self.__snapshots + else: + snapshots = [] + regexpattern = "^%s@.*%s" % (self.name, pattern) + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append(snapname) + return snapshots + + def set_auto_snap(self, include, inherit = False): + if inherit == True: + self.unset_user_property("com.sun:auto-snapshot") + else: + if include == True: + value = "true" + else: + value = "false" + self.set_user_property("com.sun:auto-snapshot", value) + + return + + +class Filesystem(ReadWritableDataset): + """ZFS Filesystem class""" + def __init__(self, name, mountpoint = None): + ReadWritableDataset.__init__(self, name) + self.__mountpoint = mountpoint + + def __str__(self): + return_string = "Filesystem name: " + self.name + \ + "\n\tMountpoint: " + self.get_mountpoint() + \ + "\n\tMounted: " + str(self.is_mounted()) + \ + "\n\tAuto snap: " + str(self.get_auto_snap()) + return return_string + + def get_mountpoint(self): + if (self.__mountpoint == None): + cmd = [ZFSCMD, "get", "-H", "-o", "value", "mountpoint", \ + self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + self.__mountpoint = result + return self.__mountpoint + + def is_mounted(self): + cmd = [ZFSCMD, "get", "-H", "-o", "value", "mounted", \ + self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + if result == "yes": + return True + else: + return False + + def list_children(self): + cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem", "-o", "name", + self.name] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if line.rstrip() != self.name: + result.append(line.rstrip()) + return result + + +class Volume(ReadWritableDataset): + """ + ZFS Volume Class + This is basically just a stub and does nothing + unique from ReadWritableDataset parent class. + """ + def __init__(self, name): + ReadWritableDataset.__init__(self, name) + + def __str__(self): + return_string = "Volume name: " + self.name + "\n" + return return_string + + +class ZFSError(Exception): + """Generic base class for ZPoolFaultedError and SnapshotError + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + self.msg = msg + def __str__(self): + return repr(self.msg) + + +class ZPoolFaultedError(ZFSError): + """Exception raised for queries made against ZPools that + are in a FAULTED state + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + ZFSError.__init__(self, msg) + + +class SnapshotError(ZFSError): + """Exception raised for invalid snapshot names provided to + Snapshot() constructor. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + ZFSError.__init__(self, msg) + + +def list_zpools(): + """Returns a list of all zpools on the system""" + result = [] + cmd = [ZPOOLCMD, "list", "-H", "-o", "name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + result.append(line.rstrip()) + return result + + +if __name__ == "__main__": + for zpool in list_zpools(): + pool = ZPool(zpool) + print pool + for filesys,mountpoint in pool.list_filesystems(): + fs = Filesystem(filesys, mountpoint) + print fs + print "\tSnapshots:" + for snapshot, snaptime in fs.list_snapshots(): + snap = Snapshot(snapshot, snaptime) + print "\t\t" + snap.name + + for volname in pool.list_volumes(): + vol = Volume(volname) + print vol + print "\tSnapshots:" + for snapshot, snaptime in vol.list_snapshots(): + snap = Snapshot(snapshot, snaptime) + print "\t\t" + snap.name + diff --git a/usr/share/time-slider/lib/time_slider/zfs.py.safe b/usr/share/time-slider/lib/time_slider/zfs.py.safe new file mode 100755 index 0000000..77b1150 --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/zfs.py.safe @@ -0,0 +1,1055 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import subprocess +import re +import threading +from bisect import insort, bisect_left, bisect_right + +import util + +BYTESPERMB = 1048576 + +# Commonly used command paths +PFCMD = "/usr/bin/pfexec" +ZFSCMD = "/usr/sbin/zfs" +ZPOOLCMD = "/usr/sbin/zpool" + + +class Datasets(Exception): + """ + Container class for all zfs datasets. Maintains a centralised + list of datasets (generated on demand) and accessor methods. + Also allows clients to notify when a refresh might be necessary. + """ + # Class wide instead of per-instance in order to avoid duplication + filesystems = None + volumes = None + snapshots = None + + # Mutex locks to prevent concurrent writes to above class wide + # dataset lists. + _filesystemslock = threading.Lock() + _volumeslock = threading.Lock() + snapshotslock = threading.Lock() + + def create_auto_snapshot_set(self, label, tag = None): + """ + Create a complete set of snapshots as if this were + for a standard zfs-auto-snapshot operation. + + Keyword arguments: + label: + A label to apply to the snapshot name. Cannot be None. + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" for will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + everything = [] + included = [] + excluded = [] + single = [] + recursive = [] + finalrecursive = [] + + # Get auto-snap property in two passes. First with the schedule + # specific tag override value, then with the general property value + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name,com.sun:auto-snapshot", "-s", "name"] + if tag: + overrideprop = "com.sun:auto-snapshot:" + tag + scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name," + overrideprop, "-s", "name"] + outdata,errdata = util.run_command(scmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + # Skip over unset values. + if line[1] == "-": + continue + # Add to everything list. This is used later + # for identifying parents/children of a given + # filesystem or volume. + everything.append(line[0]) + if line[1] == "true": + included.append(line[0]) + elif line[1] == "false": + excluded.append(line[0]) + # Now use the general property. If no value + # was set in the first pass, we set it here. + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + idx = bisect_right(everything, line[0]) + if len(everything) == 0 or \ + everything[idx-1] != line[0]: + # Dataset is neither included nor excluded so far + if line[1] == "-": + continue + everything.insert(idx, line[0]) + if line[1] == "true": + included.insert(0, line[0]) + elif line[1] == "false": + excluded.append(line[0]) + + # Now figure out what can be recursively snapshotted and what + # must be singly snapshotted. Single snapshot restrictions apply + # to those datasets who have a child in the excluded list. + # 'included' is sorted in reverse alphabetical order. + for datasetname in included: + excludedchild = False + idx = bisect_right(everything, datasetname) + children = [name for name in everything[idx:] if \ + name.find(datasetname) == 0] + for child in children: + idx = bisect_left(excluded, child) + if excluded[idx] == child: + excludedchild = True + single.append(datasetname) + break + if excludedchild == False: + # We want recursive list sorted in alphabetical order + # so insert instead of append to the list. + recursive.insert(0, datasetname) + + for datasetname in recursive: + parts = datasetname.rsplit('/', 1) + parent = parts[0] + if parent == datasetname: + # Root filesystem of the Zpool, so + # this can't be inherited and must be + # set locally. + finalrecursive.append(datasetname) + continue + idx = bisect_right(recursive, parent) + if len(recursive) > 0 and \ + recursive[idx-1] == parent: + # Parent already marked for recursive snapshot: so skip + continue + else: + finalrecursive.append(datasetname) + + for name in finalrecursive: + dataset = ReadWritableDataset(name) + dataset.create_snapshot(label, True) + for name in single: + dataset = ReadWritableDataset(name) + dataset.create_snapshot(label, False) + + def list_auto_snapshot_sets(self, tag = None): + """ + Returns a list of zfs filesystems and volumes tagged with + the "com.sun:auto-snapshot" property set to "true", either + set locally or inherited. Snapshots are excluded from the + returned result. + + Keyword Arguments: + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + #Get auto-snap property in two passes. First with the global + #value, then overriding with the label/schedule specific value + + included = [] + excluded = [] + + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name,com.sun:auto-snapshot", "-s", "name"] + if tag: + overrideprop = "com.sun:auto-snapshot:" + tag + scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name," + overrideprop, "-s", "name"] + outdata,errdata = util.run_command(scmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + if line[1] == "true": + included.append(line[0]) + elif line[1] == "false": + excluded.append(line[0]) + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + # Only set values that aren't already set. Don't override + try: + included.index(line[0]) + continue + except ValueError: + try: + excluded.index(line[0]) + continue + except ValueError: + # Dataset is not listed in either list. + if line[1] == "true": + included.append(line[0]) + return included + + def list_filesystems(self, pattern = None): + """ + List pattern matching filesystems sorted by name. + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + filesystems = [] + # Need to first ensure no other thread is trying to + # build this list at the same time. + Datasets._filesystemslock.acquire() + if Datasets.filesystems == None: + Datasets.filesystems = [] + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem", \ + "-o", "name,mountpoint", "-s", "name"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets._filesystemslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for line in outdata.rstrip().split('\n'): + line = line.rstrip().split() + Datasets.filesystems.append([line[0], line[1]]) + Datasets._filesystemslock.release() + + if pattern == None: + filesystems = Datasets.filesystems[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*%s.*" % pattern + patternobj = re.compile(regexpattern) + + for fsname,fsmountpoint in Datasets.filesystems: + patternmatchobj = re.match(patternobj, fsname) + if patternmatchobj != None: + filesystems.append(fsname, fsmountpoint) + return filesystems + + def list_volumes(self, pattern = None): + """ + List pattern matching volumes sorted by name. + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + volumes = [] + Datasets._volumeslock.acquire() + if Datasets.volumes == None: + Datasets.volumes = [] + cmd = [ZFSCMD, "list", "-H", "-t", "volume", \ + "-o", "name", "-s", "name"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets._volumeslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for line in outdata.rstrip().split('\n'): + Datasets.volumes.append(line.rstrip()) + Datasets._volumeslock.release() + + if pattern == None: + volumes = Datasets.volumes[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*%s.*" % pattern + patternobj = re.compile(regexpattern) + + for volname in Datasets.volumes: + patternmatchobj = re.match(patternobj, volname) + if patternmatchobj != None: + volumes.append(volname) + return volumes + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + snapshots = [] + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + Datasets.snapshots = [] + snaps = [] + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value,name", "creation"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err= p.wait() + except OSError, message: + Datasets.snapshotslock.release() + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets.snapshotslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for dataset in outdata.rstrip().split('\n'): + if re.search("@", dataset): + insort(snaps, dataset.split()) + for snap in snaps: + Datasets.snapshots.append([snap[1], long(snap[0])]) + if pattern == None: + snapshots = Datasets.snapshots[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*@.*%s" % pattern + patternobj = re.compile(regexpattern) + + for snapname,snaptime in Datasets.snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append([snapname, snaptime]) + Datasets.snapshotslock.release() + return snapshots + + def list_cloned_snapshots(self): + """ + Returns a list of snapshots that have cloned filesystems + dependent on them. + Snapshots with cloned filesystems can not be destroyed + unless dependent cloned filesystems are first destroyed. + """ + cmd = [ZFSCMD, "list", "-H", "-o", "origin"] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + details = line.rstrip() + if details != "-": + try: + result.index(details) + except ValueError: + result.append(details) + return result + + def list_held_snapshots(self): + """ + Returns a list of snapshots that have a "userrefs" + property value of greater than 0. Resul list is + sorted in order of creation time. Oldest listed first. + """ + cmd = [ZFSCMD, "list", "-H", + "-t", "snapshot", + "-s", "creation", + "-o", "userrefs,name"] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + details = line.split() + if details[0] != "0": + result.append(details[1]) + return result + + def refresh_snapshots(self): + """ + Should be called when snapshots have been created or deleted + and a rescan should be performed. Rescan gets deferred until + next invocation of zfs.Dataset.list_snapshots() + """ + # FIXME in future. + # This is a little sub-optimal because we should be able to modify + # the snapshot list in place in some situations and regenerate the + # snapshot list without calling out to zfs(1m). But on the + # pro side, we will pick up any new snapshots since the last + # scan that we would be otherwise unaware of. + Datasets.snapshotslock.acquire() + Datasets.snapshots = None + Datasets.snapshotslock.release() + + +class ZPool: + """ + Base class for ZFS storage pool objects + """ + def __init__(self, name): + self.name = name + self.health = self.__get_health() + self.__datasets = Datasets() + self.__filesystems = None + self.__volumes = None + self.__snapshots = None + + def __get_health(self): + """ + Returns pool health status: 'ONLINE', 'DEGRADED' or 'FAULTED' + """ + cmd = [ZPOOLCMD, "list", "-H", "-o", "health", self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + return result + + def get_capacity(self): + """ + Returns the percentage of total pool storage in use. + Calculated based on the "used" and "available" properties + of the pool's top-level filesystem because the values account + for reservations and quotas of children in their calculations, + giving a more practical indication of how much capacity is used + up on the pool. + """ + if self.health == "FAULTED": + raise ZPoolFaultedError("Can not determine capacity of zpool: %s" \ + "because it is in a FAULTED state" \ + % (self.name)) + + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", \ + "used,available", self.name] + outdata,errdata = util.run_command(cmd) + _used,_available = outdata.rstrip().split('\n') + used = float(_used) + available = float(_available) + return 100.0 * used/(used + available) + + def get_available_size(self): + """ + How much unused space is available for use on this Zpool. + Answer in bytes. + """ + # zpool(1) doesn't report available space in + # units suitable for calulations but zfs(1) + # can so use it to find the value for the + # filesystem matching the pool. + # The root filesystem of the pool is simply + # the pool name. + poolfs = Filesystem(self.name) + avail = poolfs.get_available_size() + return avail + + def get_used_size(self): + """ + How much space is in use on this Zpool. + Answer in bytes + """ + # Same as ZPool.get_available_size(): zpool(1) + # doesn't generate suitable out put so use + # zfs(1) on the toplevel filesystem + if self.health == "FAULTED": + raise ZPoolFaultedError("Can not determine used size of zpool: %s" \ + "because it is in a FAULTED state" \ + % (self.name)) + poolfs = Filesystem(self.name) + used = poolfs.get_used_size() + return used + + def list_filesystems(self): + """ + Return a list of filesystems on this Zpool. + List is sorted by name. + """ + if self.__filesystems == None: + result = [] + # Provides pre-sorted filesystem list + for fsname,fsmountpoint in self.__datasets.list_filesystems(): + if re.match(self.name, fsname): + result.append([fsname, fsmountpoint]) + self.__filesystems = result + return self.__filesystems + + def list_volumes(self): + """ + Return a list of volumes (zvol) on this Zpool + List is sorted by name + """ + if self.__volumes == None: + result = [] + regexpattern = "^%s" % self.name + patternobj = re.compile(regexpattern) + for volname in self.__datasets.list_volumes(): + patternmatchobj = re.match(patternobj, volname) + if patternmatchobj != None: + result.append(volname) + result.sort() + self.__volumes = result + return self.__volumes + + def list_auto_snapshot_sets(self, tag = None): + """ + Returns a list of zfs filesystems and volumes tagged with + the "com.sun:auto-snapshot" property set to "true", either + set locally or inherited. Snapshots are excluded from the + returned result. Results are not sorted. + + Keyword Arguments: + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + result = [] + allsets = self.__datasets.list_auto_snapshot_sets(tag) + if len(allsets) == 0: + return result + + regexpattern = "^%s" % self.name + patternobj = re.compile(regexpattern) + for datasetname in allsets: + patternmatchobj = re.match(patternobj, datasetname) + if patternmatchobj != None: + result.append(datasetname) + return result + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + # If there isn't a list of snapshots for this dataset + # already, create it now and store it in order to save + # time later for potential future invocations. + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + self.__snapshots = None + Datasets.snapshotslock.release() + if self.__snapshots == None: + result = [] + regexpattern = "^%s.*@" % self.name + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__datasets.list_snapshots(): + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + result.append([snapname, snaptime]) + # Results already sorted by creation time + self.__snapshots = result + if pattern == None: + return self.__snapshots + else: + snapshots = [] + regexpattern = "^%s.*@.*%s" % (self.name, pattern) + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append([snapname, snaptime]) + return snapshots + + def __str__(self): + return_string = "ZPool name: " + self.name + return_string = return_string + "\n\tHealth: " + self.health + try: + return_string = return_string + \ + "\n\tUsed: " + \ + str(self.get_used_size()/BYTESPERMB) + "Mb" + return_string = return_string + \ + "\n\tAvailable: " + \ + str(self.get_available_size()/BYTESPERMB) + "Mb" + return_string = return_string + \ + "\n\tCapacity: " + \ + str(self.get_capacity()) + "%" + except ZPoolFaultedError: + pass + return return_string + + +class ReadableDataset: + """ + Base class for Filesystem, Volume and Snapshot classes + Provides methods for read only operations common to all. + """ + def __init__(self, name, creation = None): + self.name = name + self.__creationTime = creation + self.datasets = Datasets() + self.__used_size = None + + def __str__(self): + return_string = "ReadableDataset name: " + self.name + "\n" + return return_string + + def get_creation_time(self): + if self.__creationTime == None: + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "creation", + self.name] + outdata,errdata = util.run_command(cmd) + self.__creationTime = long(outdata.rstrip()) + return self.__creationTime + + def exists(self): + """ + Returns True if the dataset is still existent on the system. + False otherwise + """ + # Test existance of the dataset by checking the output of a + # simple zfs get command on the snapshot + cmd = [ZFSCMD, "get", "-H", "-o", "name", "type", self.name] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (command, str(message)) + if err != 0: + # Doesn't exist + return False + + result = outdata.rstrip() + if result == self.name: + return True + else: + return False + + def get_used_size(self): + #print "fix get_used_size" + return 0 + if self.__used_size == None: + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "used", self.name] + outdata,errdata = util.run_command(cmd) + self.__used_size = long(outdata.rstrip()) + return self.__used_size + + def get_user_property(self, prop, local=False): + if local == True: + cmd = [ZFSCMD, "get", "-s", "local", "-H", "-o", "value", prop, self.name] + else: + cmd = [ZFSCMD, "get", "-H", "-o", "value", prop, self.name] + outdata,errdata = util.run_command(cmd) + return outdata.rstrip() + + def set_user_property(self, prop, value): + cmd = [PFCMD, ZFSCMD, "set", "%s=%s" % (prop, value), self.name] + outdata,errdata = util.run_command(cmd) + + def unset_user_property(self, prop): + cmd = [PFCMD, ZFSCMD, "inherit", prop, self.name] + outdata,errdata = util.run_command(cmd) + +class Snapshot(ReadableDataset): + """ + ZFS Snapshot object class. + Provides information and operations specfic to ZFS snapshots + """ + def __init__(self, name, creation = None): + """ + Keyword arguments: + name -- Name of the ZFS snapshot + creation -- Creation time of the snapshot if known (Default None) + """ + ReadableDataset.__init__(self, name, creation) + self.fsname, self.snaplabel = self.__split_snapshot_name() + self.poolname = self.__get_pool_name() + + def __get_pool_name(self): + name = self.fsname.split("/", 1) + return name[0] + + def __split_snapshot_name(self): + name = self.name.split("@", 1) + # Make sure this is really a snapshot and not a + # filesystem otherwise a filesystem could get + # destroyed instead of a snapshot. That would be + # really really bad. + if name[0] == self.name: + raise SnapshotError("\'%s\' is not a valid snapshot name" \ + % (self.name)) + return name[0],name[1] + + def get_referenced_size(self): + """ + How much unique storage space is used by this snapshot. + Answer in bytes + """ + cmd = [ZFSCMD, "get", "-H", "-p", \ + "-o", "value", "referenced", \ + self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def list_children(self): + """Returns a recursive list of child snapshots of this snapshot""" + cmd = [ZFSCMD, + "list", "-t", "snapshot", "-H", "-r", "-o", "name", + self.fsname] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if re.search("@%s" % (self.snaplabel), line) and \ + line != self.name: + result.append(line) + return result + + def has_clones(self): + """Returns True if the snapshot has any dependent clones""" + cmd = [ZFSCMD, "list", "-H", "-o", "origin,name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + details = line.rstrip().split() + if details[0] == self.name and \ + details[1] != '-': + return True + return False + + def destroy_snapshot(self, deferred=True): + """ + Permanently remove this snapshot from the filesystem + Performs deferred destruction by default. + """ + print "destroy_snapshot %s" % self.name + return + # Be sure it genuninely exists before trying to destroy it + if self.exists() == False: + return + if deferred == False: + cmd = [PFCMD, ZFSCMD, "destroy", self.name] + else: + cmd = [PFCMD, ZFSCMD, "destroy", "-d", self.name] + + outdata,errdata = util.run_command(cmd) + # Clear the global snapshot cache so that a rescan will be + # triggered on the next call to Datasets.list_snapshots() + self.datasets.refresh_snapshots() + + def hold(self, tag): + """ + Place a hold on the snapshot with the specified "tag" string. + """ + # FIXME - fails if hold is already held + # Be sure it genuninely exists before trying to place a hold + if self.exists() == False: + return + + cmd = [PFCMD, ZFSCMD, "hold", tag, self.name] + outdata,errdata = util.run_command(cmd) + + def holds(self): + """ + Returns a list of user hold tags for this snapshot + """ + cmd = [ZFSCMD, "holds", self.name] + results = [] + outdata,errdata = util.run_command(cmd) + + for line in outdata.rstrip().split('\n'): + if len(line) == 0: + continue + # The first line heading columns are NAME TAG TIMESTAMP + # Filter that line out. + line = line.split() + if (line[0] != "NAME" and line[1] != "TAG"): + results.append(line[1]) + return results + + def release(self, tag,): + """ + Release the hold on the snapshot with the specified "tag" string. + """ + # FIXME raises exception if no hold exists. + # Be sure it genuninely exists before trying to destroy it + if self.exists() == False: + return + + cmd = [PFCMD, ZFSCMD, "release", tag, self.name] + + outdata,errdata = util.run_command(cmd) + # Releasing the snapshot might cause it get automatically + # deleted by zfs. + # Clear the global snapshot cache so that a rescan will be + # triggered on the next call to Datasets.list_snapshots() + self.datasets.refresh_snapshots() + + + def __str__(self): + return_string = "Snapshot name: " + self.name + return_string = return_string + "\n\tCreation time: " \ + + str(self.get_creation_time()) + return_string = return_string + "\n\tUsed Size: " \ + + str(self.get_used_size()) + return_string = return_string + "\n\tReferenced Size: " \ + + str(self.get_referenced_size()) + return return_string + + +class ReadWritableDataset(ReadableDataset): + """ + Base class for ZFS filesystems and volumes. + Provides methods for operations and properties + common to both filesystems and volumes. + """ + def __init__(self, name, creation = None): + ReadableDataset.__init__(self, name, creation) + self.__snapshots = None + + def __str__(self): + return_string = "ReadWritableDataset name: " + self.name + "\n" + return return_string + + def get_auto_snap(self, schedule = None): + if schedule: + cmd = [ZFSCMD, "get", "-H", "-o", "value", \ + "com.sun:auto-snapshot", self.name] + cmd = [ZFSCMD, "get", "-H", "-o", "value", \ + "com.sun:auto-snapshot", self.name] + outdata,errdata = util.run_command(cmd) + if outdata.rstrip() == "true": + return True + else: + return False + + def get_available_size(self): + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "available", \ + self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def create_snapshot(self, snaplabel, recursive = False): + """ + Create a snapshot for the ReadWritable dataset using the supplied + snapshot label. + + Keyword Arguments: + snaplabel: + A string to use as the snapshot label. + The bit that comes after the "@" part of the snapshot + name. + recursive: + Recursively snapshot childfren of this dataset. + Default = False + """ + cmd = [PFCMD, ZFSCMD, "snapshot"] + if recursive == True: + cmd.append("-r") + cmd.append("%s@%s" % (self.name, snaplabel)) + outdata,errdata = util.run_command(cmd) + self.datasets.refresh_snapshots() + + def list_children(self): + + # Note, if more dataset types ever come around they will + # need to be added to the filsystem,volume args below. + # Not for the forseeable future though. + cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem,volume", + "-o", "name", self.name] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if line.rstrip() != self.name: + result.append(line.rstrip()) + return result + + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + # If there isn't a list of snapshots for this dataset + # already, create it now and store it in order to save + # time later for potential future invocations. + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + self.__snapshots = None + Datasets.snapshotslock.release() + if self.__snapshots == None: + result = [] + regexpattern = "^%s@" % self.name + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.datasets.list_snapshots(): + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + result.append([snapname, snaptime]) + # Results already sorted by creation time + self.__snapshots = result + if pattern == None: + return self.__snapshots + else: + snapshots = [] + regexpattern = "^%s@.*%s" % (self.name, pattern) + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append(snapname) + return snapshots + + def set_auto_snap(self, include, inherit = False): + if inherit == True: + self.unset_user_property("com.sun:auto-snapshot") + else: + if include == True: + value = "true" + else: + value = "false" + self.set_user_property("com.sun:auto-snapshot", value) + + return + + +class Filesystem(ReadWritableDataset): + """ZFS Filesystem class""" + def __init__(self, name, mountpoint = None): + ReadWritableDataset.__init__(self, name) + self.__mountpoint = mountpoint + + def __str__(self): + return_string = "Filesystem name: " + self.name + \ + "\n\tMountpoint: " + self.get_mountpoint() + \ + "\n\tMounted: " + str(self.is_mounted()) + \ + "\n\tAuto snap: " + str(self.get_auto_snap()) + return return_string + + def get_mountpoint(self): + if (self.__mountpoint == None): + cmd = [ZFSCMD, "get", "-H", "-o", "value", "mountpoint", \ + self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + self.__mountpoint = result + return self.__mountpoint + + def is_mounted(self): + cmd = [ZFSCMD, "get", "-H", "-o", "value", "mounted", \ + self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + if result == "yes": + return True + else: + return False + + def list_children(self): + cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem", "-o", "name", + self.name] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if line.rstrip() != self.name: + result.append(line.rstrip()) + return result + + +class Volume(ReadWritableDataset): + """ + ZFS Volume Class + This is basically just a stub and does nothing + unique from ReadWritableDataset parent class. + """ + def __init__(self, name): + ReadWritableDataset.__init__(self, name) + + def __str__(self): + return_string = "Volume name: " + self.name + "\n" + return return_string + + +class ZFSError(Exception): + """Generic base class for ZPoolFaultedError and SnapshotError + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + self.msg = msg + def __str__(self): + return repr(self.msg) + + +class ZPoolFaultedError(ZFSError): + """Exception raised for queries made against ZPools that + are in a FAULTED state + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + ZFSError.__init__(self, msg) + + +class SnapshotError(ZFSError): + """Exception raised for invalid snapshot names provided to + Snapshot() constructor. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + ZFSError.__init__(self, msg) + + +def list_zpools(): + """Returns a list of all zpools on the system""" + result = [] + cmd = [ZPOOLCMD, "list", "-H", "-o", "name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + result.append(line.rstrip()) + return result + + +if __name__ == "__main__": + for zpool in list_zpools(): + pool = ZPool(zpool) + print pool + for filesys,mountpoint in pool.list_filesystems(): + fs = Filesystem(filesys, mountpoint) + print fs + print "\tSnapshots:" + for snapshot, snaptime in fs.list_snapshots(): + snap = Snapshot(snapshot, snaptime) + print "\t\t" + snap.name + + for volname in pool.list_volumes(): + vol = Volume(volname) + print vol + print "\tSnapshots:" + for snapshot, snaptime in vol.list_snapshots(): + snap = Snapshot(snapshot, snaptime) + print "\t\t" + snap.name + + diff --git a/usr/share/time-slider/lib/time_slider/zfs.py~ b/usr/share/time-slider/lib/time_slider/zfs.py~ new file mode 100755 index 0000000..b35e58d --- /dev/null +++ b/usr/share/time-slider/lib/time_slider/zfs.py~ @@ -0,0 +1,1049 @@ +#!/usr/bin/python2.6 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +import subprocess +import re +import threading +from bisect import insort, bisect_left, bisect_right + +import util + +BYTESPERMB = 1048576 + +# Commonly used command paths +PFCMD = "/usr/bin/pfexec" +ZFSCMD = "/usr/sbin/zfs" +ZPOOLCMD = "/usr/sbin/zpool" + + +class Datasets(Exception): + """ + Container class for all zfs datasets. Maintains a centralised + list of datasets (generated on demand) and accessor methods. + Also allows clients to notify when a refresh might be necessary. + """ + # Class wide instead of per-instance in order to avoid duplication + filesystems = None + volumes = None + snapshots = None + + # Mutex locks to prevent concurrent writes to above class wide + # dataset lists. + _filesystemslock = threading.Lock() + _volumeslock = threading.Lock() + snapshotslock = threading.Lock() + + def create_auto_snapshot_set(self, label, tag = None): + """ + Create a complete set of snapshots as if this were + for a standard zfs-auto-snapshot operation. + + Keyword arguments: + label: + A label to apply to the snapshot name. Cannot be None. + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" for will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + everything = [] + included = [] + excluded = [] + single = [] + recursive = [] + finalrecursive = [] + + # Get auto-snap property in two passes. First with the schedule + # specific tag override value, then with the general property value + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name,com.sun:auto-snapshot", "-s", "name"] + if tag: + overrideprop = "com.sun:auto-snapshot:" + tag + scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name," + overrideprop, "-s", "name"] + outdata,errdata = util.run_command(scmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + # Skip over unset values. + if line[1] == "-": + continue + # Add to everything list. This is used later + # for identifying parents/children of a given + # filesystem or volume. + everything.append(line[0]) + if line[1] == "true": + included.append(line[0]) + elif line[1] == "false": + excluded.append(line[0]) + # Now use the general property. If no value + # was set in the first pass, we set it here. + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + idx = bisect_right(everything, line[0]) + if len(everything) == 0 or \ + everything[idx-1] != line[0]: + # Dataset is neither included nor excluded so far + if line[1] == "-": + continue + everything.insert(idx, line[0]) + if line[1] == "true": + included.insert(0, line[0]) + elif line[1] == "false": + excluded.append(line[0]) + + # Now figure out what can be recursively snapshotted and what + # must be singly snapshotted. Single snapshot restrictions apply + # to those datasets who have a child in the excluded list. + # 'included' is sorted in reverse alphabetical order. + for datasetname in included: + excludedchild = False + idx = bisect_right(everything, datasetname) + children = [name for name in everything[idx:] if \ + name.find(datasetname) == 0] + for child in children: + idx = bisect_left(excluded, child) + if idx < len(excluded) and excluded[idx] == child: + excludedchild = True + single.append(datasetname) + break + if excludedchild == False: + # We want recursive list sorted in alphabetical order + # so insert instead of append to the list. + recursive.insert(0, datasetname) + + for datasetname in recursive: + parts = datasetname.rsplit('/', 1) + parent = parts[0] + if parent == datasetname: + # Root filesystem of the Zpool, so + # this can't be inherited and must be + # set locally. + finalrecursive.append(datasetname) + continue + idx = bisect_right(recursive, parent) + if len(recursive) > 0 and \ + recursive[idx-1] == parent: + # Parent already marked for recursive snapshot: so skip + continue + else: + finalrecursive.append(datasetname) + + for name in finalrecursive: + dataset = ReadWritableDataset(name) + dataset.create_snapshot(label, True) + for name in single: + dataset = ReadWritableDataset(name) + dataset.create_snapshot(label, False) + + def list_auto_snapshot_sets(self, tag = None): + """ + Returns a list of zfs filesystems and volumes tagged with + the "com.sun:auto-snapshot" property set to "true", either + set locally or inherited. Snapshots are excluded from the + returned result. + + Keyword Arguments: + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + #Get auto-snap property in two passes. First with the global + #value, then overriding with the label/schedule specific value + + included = [] + excluded = [] + + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name,com.sun:auto-snapshot", "-s", "name"] + if tag: + overrideprop = "com.sun:auto-snapshot:" + tag + scmd = [ZFSCMD, "list", "-H", "-t", "filesystem,volume", + "-o", "name," + overrideprop, "-s", "name"] + outdata,errdata = util.run_command(scmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + if line[1] == "true": + included.append(line[0]) + elif line[1] == "false": + excluded.append(line[0]) + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + line = line.split() + # Only set values that aren't already set. Don't override + try: + included.index(line[0]) + continue + except ValueError: + try: + excluded.index(line[0]) + continue + except ValueError: + # Dataset is not listed in either list. + if line[1] == "true": + included.append(line[0]) + return included + + def list_filesystems(self, pattern = None): + """ + List pattern matching filesystems sorted by name. + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + filesystems = [] + # Need to first ensure no other thread is trying to + # build this list at the same time. + Datasets._filesystemslock.acquire() + if Datasets.filesystems == None: + Datasets.filesystems = [] + cmd = [ZFSCMD, "list", "-H", "-t", "filesystem", \ + "-o", "name,mountpoint", "-s", "name"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets._filesystemslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for line in outdata.rstrip().split('\n'): + line = line.rstrip().split() + Datasets.filesystems.append([line[0], line[1]]) + Datasets._filesystemslock.release() + + if pattern == None: + filesystems = Datasets.filesystems[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*%s.*" % pattern + patternobj = re.compile(regexpattern) + + for fsname,fsmountpoint in Datasets.filesystems: + patternmatchobj = re.match(patternobj, fsname) + if patternmatchobj != None: + filesystems.append(fsname, fsmountpoint) + return filesystems + + def list_volumes(self, pattern = None): + """ + List pattern matching volumes sorted by name. + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + volumes = [] + Datasets._volumeslock.acquire() + if Datasets.volumes == None: + Datasets.volumes = [] + cmd = [ZFSCMD, "list", "-H", "-t", "volume", \ + "-o", "name", "-s", "name"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets._volumeslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for line in outdata.rstrip().split('\n'): + Datasets.volumes.append(line.rstrip()) + Datasets._volumeslock.release() + + if pattern == None: + volumes = Datasets.volumes[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*%s.*" % pattern + patternobj = re.compile(regexpattern) + + for volname in Datasets.volumes: + patternmatchobj = re.match(patternobj, volname) + if patternmatchobj != None: + volumes.append(volname) + return volumes + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + snapshots = [] + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + Datasets.snapshots = [] + snaps = [] + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value,name", "creation"] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err= p.wait() + except OSError, message: + Datasets.snapshotslock.release() + raise RuntimeError, "%s subprocess error:\n %s" % \ + (cmd, str(message)) + if err != 0: + Datasets.snapshotslock.release() + raise RuntimeError, '%s failed with exit code %d\n%s' % \ + (str(cmd), err, errdata) + for dataset in outdata.rstrip().split('\n'): + if re.search("@", dataset): + insort(snaps, dataset.split()) + for snap in snaps: + Datasets.snapshots.append([snap[1], long(snap[0])]) + if pattern == None: + snapshots = Datasets.snapshots[:] + else: + # Regular expression pattern to match "pattern" parameter. + regexpattern = ".*@.*%s" % pattern + patternobj = re.compile(regexpattern) + + for snapname,snaptime in Datasets.snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append([snapname, snaptime]) + Datasets.snapshotslock.release() + return snapshots + + def list_cloned_snapshots(self): + """ + Returns a list of snapshots that have cloned filesystems + dependent on them. + Snapshots with cloned filesystems can not be destroyed + unless dependent cloned filesystems are first destroyed. + """ + cmd = [ZFSCMD, "list", "-H", "-o", "origin"] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + details = line.rstrip() + if details != "-": + try: + result.index(details) + except ValueError: + result.append(details) + return result + + def list_held_snapshots(self): + """ + Returns a list of snapshots that have a "userrefs" + property value of greater than 0. Resul list is + sorted in order of creation time. Oldest listed first. + """ + cmd = [ZFSCMD, "list", "-H", + "-t", "snapshot", + "-s", "creation", + "-o", "userrefs,name"] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + details = line.split() + if details[0] != "0": + result.append(details[1]) + return result + + def refresh_snapshots(self): + """ + Should be called when snapshots have been created or deleted + and a rescan should be performed. Rescan gets deferred until + next invocation of zfs.Dataset.list_snapshots() + """ + # FIXME in future. + # This is a little sub-optimal because we should be able to modify + # the snapshot list in place in some situations and regenerate the + # snapshot list without calling out to zfs(1m). But on the + # pro side, we will pick up any new snapshots since the last + # scan that we would be otherwise unaware of. + Datasets.snapshotslock.acquire() + Datasets.snapshots = None + Datasets.snapshotslock.release() + + +class ZPool: + """ + Base class for ZFS storage pool objects + """ + def __init__(self, name): + self.name = name + self.health = self.__get_health() + self.__datasets = Datasets() + self.__filesystems = None + self.__volumes = None + self.__snapshots = None + + def __get_health(self): + """ + Returns pool health status: 'ONLINE', 'DEGRADED' or 'FAULTED' + """ + cmd = [ZPOOLCMD, "list", "-H", "-o", "health", self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + return result + + def get_capacity(self): + """ + Returns the percentage of total pool storage in use. + Calculated based on the "used" and "available" properties + of the pool's top-level filesystem because the values account + for reservations and quotas of children in their calculations, + giving a more practical indication of how much capacity is used + up on the pool. + """ + if self.health == "FAULTED": + raise ZPoolFaultedError("Can not determine capacity of zpool: %s" \ + "because it is in a FAULTED state" \ + % (self.name)) + + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", \ + "used,available", self.name] + outdata,errdata = util.run_command(cmd) + _used,_available = outdata.rstrip().split('\n') + used = float(_used) + available = float(_available) + return 100.0 * used/(used + available) + + def get_available_size(self): + """ + How much unused space is available for use on this Zpool. + Answer in bytes. + """ + # zpool(1) doesn't report available space in + # units suitable for calulations but zfs(1) + # can so use it to find the value for the + # filesystem matching the pool. + # The root filesystem of the pool is simply + # the pool name. + poolfs = Filesystem(self.name) + avail = poolfs.get_available_size() + return avail + + def get_used_size(self): + """ + How much space is in use on this Zpool. + Answer in bytes + """ + # Same as ZPool.get_available_size(): zpool(1) + # doesn't generate suitable out put so use + # zfs(1) on the toplevel filesystem + if self.health == "FAULTED": + raise ZPoolFaultedError("Can not determine used size of zpool: %s" \ + "because it is in a FAULTED state" \ + % (self.name)) + poolfs = Filesystem(self.name) + used = poolfs.get_used_size() + return used + + def list_filesystems(self): + """ + Return a list of filesystems on this Zpool. + List is sorted by name. + """ + if self.__filesystems == None: + result = [] + # Provides pre-sorted filesystem list + for fsname,fsmountpoint in self.__datasets.list_filesystems(): + if re.match(self.name, fsname): + result.append([fsname, fsmountpoint]) + self.__filesystems = result + return self.__filesystems + + def list_volumes(self): + """ + Return a list of volumes (zvol) on this Zpool + List is sorted by name + """ + if self.__volumes == None: + result = [] + regexpattern = "^%s" % self.name + patternobj = re.compile(regexpattern) + for volname in self.__datasets.list_volumes(): + patternmatchobj = re.match(patternobj, volname) + if patternmatchobj != None: + result.append(volname) + result.sort() + self.__volumes = result + return self.__volumes + + def list_auto_snapshot_sets(self, tag = None): + """ + Returns a list of zfs filesystems and volumes tagged with + the "com.sun:auto-snapshot" property set to "true", either + set locally or inherited. Snapshots are excluded from the + returned result. Results are not sorted. + + Keyword Arguments: + tag: + A string indicating one of the standard auto-snapshot schedules + tags to check (eg. "frequent" will map to the tag: + com.sun:auto-snapshot:frequent). If specified as a zfs property + on a zfs dataset, the property corresponding to the tag will + override the wildcard property: "com.sun:auto-snapshot" + Default value = None + """ + result = [] + allsets = self.__datasets.list_auto_snapshot_sets(tag) + if len(allsets) == 0: + return result + + regexpattern = "^%s" % self.name + patternobj = re.compile(regexpattern) + for datasetname in allsets: + patternmatchobj = re.match(patternobj, datasetname) + if patternmatchobj != None: + result.append(datasetname) + return result + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + # If there isn't a list of snapshots for this dataset + # already, create it now and store it in order to save + # time later for potential future invocations. + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + self.__snapshots = None + Datasets.snapshotslock.release() + if self.__snapshots == None: + result = [] + regexpattern = "^%s.*@" % self.name + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__datasets.list_snapshots(): + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + result.append([snapname, snaptime]) + # Results already sorted by creation time + self.__snapshots = result + if pattern == None: + return self.__snapshots + else: + snapshots = [] + regexpattern = "^%s.*@.*%s" % (self.name, pattern) + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append([snapname, snaptime]) + return snapshots + + def __str__(self): + return_string = "ZPool name: " + self.name + return_string = return_string + "\n\tHealth: " + self.health + try: + return_string = return_string + \ + "\n\tUsed: " + \ + str(self.get_used_size()/BYTESPERMB) + "Mb" + return_string = return_string + \ + "\n\tAvailable: " + \ + str(self.get_available_size()/BYTESPERMB) + "Mb" + return_string = return_string + \ + "\n\tCapacity: " + \ + str(self.get_capacity()) + "%" + except ZPoolFaultedError: + pass + return return_string + + +class ReadableDataset: + """ + Base class for Filesystem, Volume and Snapshot classes + Provides methods for read only operations common to all. + """ + def __init__(self, name, creation = None): + self.name = name + self.__creationTime = creation + self.datasets = Datasets() + + def __str__(self): + return_string = "ReadableDataset name: " + self.name + "\n" + return return_string + + def get_creation_time(self): + if self.__creationTime == None: + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "creation", + self.name] + outdata,errdata = util.run_command(cmd) + self.__creationTime = long(outdata.rstrip()) + return self.__creationTime + + def exists(self): + """ + Returns True if the dataset is still existent on the system. + False otherwise + """ + # Test existance of the dataset by checking the output of a + # simple zfs get command on the snapshot + cmd = [ZFSCMD, "get", "-H", "-o", "name", "type", self.name] + try: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + outdata,errdata = p.communicate() + err = p.wait() + except OSError, message: + raise RuntimeError, "%s subprocess error:\n %s" % \ + (command, str(message)) + if err != 0: + # Doesn't exist + return False + + result = outdata.rstrip() + if result == self.name: + return True + else: + return False + + def get_used_size(self): + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "used", self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def get_user_property(self, prop, local=False): + if local == True: + cmd = [ZFSCMD, "get", "-s", "local", "-H", "-o", "value", prop, self.name] + else: + cmd = [ZFSCMD, "get", "-H", "-o", "value", prop, self.name] + outdata,errdata = util.run_command(cmd) + return outdata.rstrip() + + def set_user_property(self, prop, value): + cmd = [PFCMD, ZFSCMD, "set", "%s=%s" % (prop, value), self.name] + outdata,errdata = util.run_command(cmd) + + def unset_user_property(self, prop): + cmd = [PFCMD, ZFSCMD, "inherit", prop, self.name] + outdata,errdata = util.run_command(cmd) + +class Snapshot(ReadableDataset): + """ + ZFS Snapshot object class. + Provides information and operations specfic to ZFS snapshots + """ + def __init__(self, name, creation = None): + """ + Keyword arguments: + name -- Name of the ZFS snapshot + creation -- Creation time of the snapshot if known (Default None) + """ + ReadableDataset.__init__(self, name, creation) + self.fsname, self.snaplabel = self.__split_snapshot_name() + self.poolname = self.__get_pool_name() + + def __get_pool_name(self): + name = self.fsname.split("/", 1) + return name[0] + + def __split_snapshot_name(self): + name = self.name.split("@", 1) + # Make sure this is really a snapshot and not a + # filesystem otherwise a filesystem could get + # destroyed instead of a snapshot. That would be + # really really bad. + if name[0] == self.name: + raise SnapshotError("\'%s\' is not a valid snapshot name" \ + % (self.name)) + return name[0],name[1] + + def get_referenced_size(self): + """ + How much unique storage space is used by this snapshot. + Answer in bytes + """ + cmd = [ZFSCMD, "get", "-H", "-p", \ + "-o", "value", "referenced", \ + self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def list_children(self): + """Returns a recursive list of child snapshots of this snapshot""" + cmd = [ZFSCMD, + "list", "-t", "snapshot", "-H", "-r", "-o", "name", + self.fsname] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if re.search("@%s" % (self.snaplabel), line) and \ + line != self.name: + result.append(line) + return result + + def has_clones(self): + """Returns True if the snapshot has any dependent clones""" + cmd = [ZFSCMD, "list", "-H", "-o", "origin,name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + details = line.rstrip().split() + if details[0] == self.name and \ + details[1] != '-': + return True + return False + + def destroy(self, deferred=True): + """ + Permanently remove this snapshot from the filesystem + Performs deferred destruction by default. + """ + # Be sure it genuninely exists before trying to destroy it + if self.exists() == False: + return + if deferred == False: + cmd = [PFCMD, ZFSCMD, "destroy", self.name] + else: + cmd = [PFCMD, ZFSCMD, "destroy", "-d", self.name] + + outdata,errdata = util.run_command(cmd) + # Clear the global snapshot cache so that a rescan will be + # triggered on the next call to Datasets.list_snapshots() + self.datasets.refresh_snapshots() + + def hold(self, tag): + """ + Place a hold on the snapshot with the specified "tag" string. + """ + # FIXME - fails if hold is already held + # Be sure it genuninely exists before trying to place a hold + if self.exists() == False: + return + + cmd = [PFCMD, ZFSCMD, "hold", tag, self.name] + outdata,errdata = util.run_command(cmd) + + def holds(self): + """ + Returns a list of user hold tags for this snapshot + """ + cmd = [ZFSCMD, "holds", self.name] + results = [] + outdata,errdata = util.run_command(cmd) + + for line in outdata.rstrip().split('\n'): + if len(line) == 0: + continue + # The first line heading columns are NAME TAG TIMESTAMP + # Filter that line out. + line = line.split() + if (line[0] != "NAME" and line[1] != "TAG"): + results.append(line[1]) + return results + + def release(self, tag,): + """ + Release the hold on the snapshot with the specified "tag" string. + """ + # FIXME raises exception if no hold exists. + # Be sure it genuninely exists before trying to destroy it + if self.exists() == False: + return + + cmd = [PFCMD, ZFSCMD, "release", tag, self.name] + + outdata,errdata = util.run_command(cmd) + # Releasing the snapshot might cause it get automatically + # deleted by zfs. + # Clear the global snapshot cache so that a rescan will be + # triggered on the next call to Datasets.list_snapshots() + self.datasets.refresh_snapshots() + + + def __str__(self): + return_string = "Snapshot name: " + self.name + return_string = return_string + "\n\tCreation time: " \ + + str(self.get_creation_time()) + return_string = return_string + "\n\tUsed Size: " \ + + str(self.get_used_size()) + return_string = return_string + "\n\tReferenced Size: " \ + + str(self.get_referenced_size()) + return return_string + + +class ReadWritableDataset(ReadableDataset): + """ + Base class for ZFS filesystems and volumes. + Provides methods for operations and properties + common to both filesystems and volumes. + """ + def __init__(self, name, creation = None): + ReadableDataset.__init__(self, name, creation) + self.__snapshots = None + + def __str__(self): + return_string = "ReadWritableDataset name: " + self.name + "\n" + return return_string + + def get_auto_snap(self, schedule = None): + if schedule: + cmd = [ZFSCMD, "get", "-H", "-o", "value", \ + "com.sun:auto-snapshot", self.name] + cmd = [ZFSCMD, "get", "-H", "-o", "value", \ + "com.sun:auto-snapshot", self.name] + outdata,errdata = util.run_command(cmd) + if outdata.rstrip() == "true": + return True + else: + return False + + def get_available_size(self): + cmd = [ZFSCMD, "get", "-H", "-p", "-o", "value", "available", \ + self.name] + outdata,errdata = util.run_command(cmd) + return long(outdata.rstrip()) + + def create_snapshot(self, snaplabel, recursive = False): + """ + Create a snapshot for the ReadWritable dataset using the supplied + snapshot label. + + Keyword Arguments: + snaplabel: + A string to use as the snapshot label. + The bit that comes after the "@" part of the snapshot + name. + recursive: + Recursively snapshot childfren of this dataset. + Default = False + """ + cmd = [PFCMD, ZFSCMD, "snapshot"] + if recursive == True: + cmd.append("-r") + cmd.append("%s@%s" % (self.name, snaplabel)) + outdata,errdata = util.run_command(cmd, False) + if errdata: + print errdata + self.datasets.refresh_snapshots() + + def list_children(self): + + # Note, if more dataset types ever come around they will + # need to be added to the filsystem,volume args below. + # Not for the forseeable future though. + cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem,volume", + "-o", "name", self.name] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if line.rstrip() != self.name: + result.append(line.rstrip()) + return result + + + def list_snapshots(self, pattern = None): + """ + List pattern matching snapshots sorted by creation date. + Oldest listed first + + Keyword arguments: + pattern -- Filter according to pattern (default None) + """ + # If there isn't a list of snapshots for this dataset + # already, create it now and store it in order to save + # time later for potential future invocations. + Datasets.snapshotslock.acquire() + if Datasets.snapshots == None: + self.__snapshots = None + Datasets.snapshotslock.release() + if self.__snapshots == None: + result = [] + regexpattern = "^%s@" % self.name + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.datasets.list_snapshots(): + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + result.append([snapname, snaptime]) + # Results already sorted by creation time + self.__snapshots = result + if pattern == None: + return self.__snapshots + else: + snapshots = [] + regexpattern = "^%s@.*%s" % (self.name, pattern) + patternobj = re.compile(regexpattern) + for snapname,snaptime in self.__snapshots: + patternmatchobj = re.match(patternobj, snapname) + if patternmatchobj != None: + snapshots.append(snapname) + return snapshots + + def set_auto_snap(self, include, inherit = False): + if inherit == True: + self.unset_user_property("com.sun:auto-snapshot") + else: + if include == True: + value = "true" + else: + value = "false" + self.set_user_property("com.sun:auto-snapshot", value) + + return + + +class Filesystem(ReadWritableDataset): + """ZFS Filesystem class""" + def __init__(self, name, mountpoint = None): + ReadWritableDataset.__init__(self, name) + self.__mountpoint = mountpoint + + def __str__(self): + return_string = "Filesystem name: " + self.name + \ + "\n\tMountpoint: " + self.get_mountpoint() + \ + "\n\tMounted: " + str(self.is_mounted()) + \ + "\n\tAuto snap: " + str(self.get_auto_snap()) + return return_string + + def get_mountpoint(self): + if (self.__mountpoint == None): + cmd = [ZFSCMD, "get", "-H", "-o", "value", "mountpoint", \ + self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + self.__mountpoint = result + return self.__mountpoint + + def is_mounted(self): + cmd = [ZFSCMD, "get", "-H", "-o", "value", "mounted", \ + self.name] + outdata,errdata = util.run_command(cmd) + result = outdata.rstrip() + if result == "yes": + return True + else: + return False + + def list_children(self): + cmd = [ZFSCMD, "list", "-H", "-r", "-t", "filesystem", "-o", "name", + self.name] + outdata,errdata = util.run_command(cmd) + result = [] + for line in outdata.rstrip().split('\n'): + if line.rstrip() != self.name: + result.append(line.rstrip()) + return result + + +class Volume(ReadWritableDataset): + """ + ZFS Volume Class + This is basically just a stub and does nothing + unique from ReadWritableDataset parent class. + """ + def __init__(self, name): + ReadWritableDataset.__init__(self, name) + + def __str__(self): + return_string = "Volume name: " + self.name + "\n" + return return_string + + +class ZFSError(Exception): + """Generic base class for ZPoolFaultedError and SnapshotError + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + self.msg = msg + def __str__(self): + return repr(self.msg) + + +class ZPoolFaultedError(ZFSError): + """Exception raised for queries made against ZPools that + are in a FAULTED state + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + ZFSError.__init__(self, msg) + + +class SnapshotError(ZFSError): + """Exception raised for invalid snapshot names provided to + Snapshot() constructor. + + Attributes: + msg -- explanation of the error + """ + def __init__(self, msg): + ZFSError.__init__(self, msg) + + +def list_zpools(): + """Returns a list of all zpools on the system""" + result = [] + cmd = [ZPOOLCMD, "list", "-H", "-o", "name"] + outdata,errdata = util.run_command(cmd) + for line in outdata.rstrip().split('\n'): + result.append(line.rstrip()) + return result + + +if __name__ == "__main__": + for zpool in list_zpools(): + pool = ZPool(zpool) + print pool + for filesys,mountpoint in pool.list_filesystems(): + fs = Filesystem(filesys, mountpoint) + print fs + print "\tSnapshots:" + for snapshot, snaptime in fs.list_snapshots(): + snap = Snapshot(snapshot, snaptime) + print "\t\t" + snap.name + + for volname in pool.list_volumes(): + vol = Volume(volname) + print vol + print "\tSnapshots:" + for snapshot, snaptime in vol.list_snapshots(): + snap = Snapshot(snapshot, snaptime) + print "\t\t" + snap.name + diff --git a/var/svc/manifest/application/time-slider-plugin.xml b/var/svc/manifest/application/time-slider-plugin.xml new file mode 100644 index 0000000..5e98b02 --- /dev/null +++ b/var/svc/manifest/application/time-slider-plugin.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/var/svc/manifest/application/time-slider.xml b/var/svc/manifest/application/time-slider.xml new file mode 100644 index 0000000..1178d6f --- /dev/null +++ b/var/svc/manifest/application/time-slider.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/var/svc/manifest/system/filesystem/auto-snapshot.xml b/var/svc/manifest/system/filesystem/auto-snapshot.xml new file mode 100644 index 0000000..01e0591 --- /dev/null +++ b/var/svc/manifest/system/filesystem/auto-snapshot.xml @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 1.8.3.1