var GLib = imports.gi.GLib; var Lang = imports.lang; var Main = imports.ui.main; var PanelMenu = imports.ui.panelMenu; var PopupMenu = imports.ui.popupMenu; var St = imports.gi.St; var Shell = imports.gi.Shell; var Gettext = imports.gettext.domain('gnome-shell-extensions'); var _ = Gettext.gettext; var ExtensionUtils = imports.misc.extensionUtils; var Me = ExtensionUtils.getCurrentExtension(); var Convenience = Me.imports.convenience; var Util = imports.misc.util; var PopupServiceItem = Me.imports.popupServiceItem.PopupServiceItem; var PopupTargetItem = Me.imports.popupTargetItem.PopupTargetItem; var PopupMenuItem = Me.imports.popupManuallyItem.PopupServiceItem; var MountMenuItem = Me.imports.popupMountItem.MountMenuItem; var DriveMenuItem = Me.imports.popupDriveItem.DriveMenuItem; var VolMenuItem = Me.imports.popupBkpVolumItem.PopupBKPItem; var Gio = imports.gi.Gio; var Mainloop = imports.mainloop; var refreshTime = 3.0; var MainLabel; var icon; var MainIcon; var extMediaName = 'external backup-drive'; var Drives = new Object(); //var DisabledIcon = 'my-caffeine-off-symbolic'; //var EnabledIcon = 'my-caffeine-on-symbolic'; var DisabledIcon = 'system-run-symbolic'; var EnabledIcon = 'system-run-symbolic'; var BackupManager = new Lang.Class({ Name: 'BackupManager', Extends: PanelMenu.Button, _entries: [], _init: function() { this._drives = [ ]; this._volumes = [ ]; this._mounts = [ ]; //Set a FileMonitor on the config-File. So the Config-File is only //read, when it changed. this.GF = Gio.File.new_for_path('/etc/mksnapshot.conf'); this._monitorConf = this.GF.monitor_file(Gio.FileMonitorFlags.NONE,null,null,null) this._monitorConf.connect("changed", Lang.bind(this, function(monitor, file, o, event) { // without this test, _loadConfig() is called more than once!! if (event == Gio.FileMonitorEvent.CHANGES_DONE_HINT && ! /~$/.test(file.get_basename())) { this._loadConfig(); } })); this._loadConfig(); this.watchvolume = this._run_command('systemd-escape --path '+this.bkpmnt)+'.mount' PanelMenu.Button.prototype._init.call(this, 0.0); var hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); MainIcon = new St.Icon({icon_name: 'drive-harddisk-usb-symbolic', style_class: 'system-status-icon'}); MainLabel = new St.Label({ text: '---', }); hbox.add_child(MainLabel); hbox.add_child(MainIcon); hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM)); this.actor.add_actor(hbox); this.actor.add_style_class_name('panel-status-button'); this.actor.connect('button-press-event', Lang.bind(this, function() { this._refresh(); })); Main.panel.addToStatusArea('backupManager', this); //this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); // Fill the Menu // First a Entry to open backup-Location var bkpitem = this.menu.addAction(_("Open Backups"), function(event) { var context = global.create_app_launch_context(event.get_time(), -1); var GF = Gio.File.new_for_path('backup'); Gio.AppInfo.launch_default_for_uri(GF.get_uri(),context); }); this.extItem = new PopupTargetItem(extMediaName, this._check_service('mkbackup@BKP.target','active')); this.extItem.connect('toggled', Lang.bind(this, function() { GLib.spawn_command_line_async( this._getCommand('mkbackup@BKP.target', (this._check_service('mkbackup@BKP.target','active') ? 'stop' : 'start'), 'system')); })); this.menu.addMenuItem(this.extItem); this.snapitem = new PopupMenu.PopupMenuItem(_("Take snapshot now")); this.snapitem.connect('activate', Lang.bind(this, function() { GLib.spawn_command_line_async( this._getCommand('mkbackup@manually.service', 'restart', 'system')); this.menu.close(); })); this.menu.addMenuItem(this.snapitem); this.bkpsubmenu = new PopupMenu.PopupSubMenuMenuItem(_("Backup-Intervalle"), true); this.bkpsubmenu.icon.icon_name = 'system-run-symbolic'; this.menu.addMenuItem(this.bkpsubmenu); if(this._entries.length > 0) this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this._refreshID = Mainloop.timeout_add_seconds(refreshTime, Lang.bind(this, this._refresh_panel)); // New Volumes to use as backup-media this._monitor = Gio.VolumeMonitor.get(); this._removedDriveId = this._monitor.connect('drive-disconnected', Lang.bind(this, function(monitor, drive) { log('DRIVE DISCONNECTED',drive.get_name(),this._removedDriveId) this._DriveRemoved(drive); //this.extItem.label.text = _("external backup-drive"); //this.drvsubmenu.destroy(); })); this._addedDriveId = this._monitor.connect('drive-connected', Lang.bind(this, function(monitor, drive) { log('DRIVE CONNECTED',drive.get_name()); this._DriveAdded(drive); })); this._addedVolumeId = this._monitor.connect('volume-added', Lang.bind(this, function(monitor, volume){ this._VolumeAdded(volume); log('VOLUMES',JSON.stringify(this._drives)) })) this._removedVolumeId = this._monitor.connect('volume-removed', Lang.bind(this, function(monitor, volume){ this._VolumeRemoved(volume); })) /*this._monitor.get_volumes().forEach(Lang.bind(this, function(volume) { this._VolumeAdded(volume); })); this._addedMountId = this._monitor.connect('mount-added', Lang.bind(this, function(monitor, mount) { log('MOUNT CONNECTED',mount.get_name()) log(mount.get_name()) var volume = mount.get_volume() log(volume.get_name(),volume.get_uuid()) //log(mount.get_name()) //this._showDrive(drive); }));*/ //log(Gio.UnixMountPoint.get_device_path('/var/cache/backup')) return; }, _DriveAdded: function(drive) { var Dident = drive.enumerate_identifiers(); var u_dev = drive.get_identifier('unix-device'); var d_name = drive.get_name(); log('DRIVE unix-device',u_dev,d_name) this._drives[d_name] = new Object() this._drives[d_name]['drive'] = drive; this._drives[d_name]['device'] = drive.get_identifier('unix-device'); //this._drives[d_name]['uuid'] = drive.get_identifier('uuid'); this._drives[d_name]['volumes'] = new Object() if (drive.has_volumes()) { log(drive.get_volumes()); var VList = drive.get_volumes(); VList.forEach(Lang.bind(this, function(volume){ var v_name = volume.get_name(); log(v_name) this._drives[d_name]['volumes'][v_name] = this._addVolume(volume); })); //VList.free() }; log('VOLUMES',JSON.stringify(this._drives)) this.drvsubmenu = new PopupMenu.PopupSubMenuMenuItem(drive.get_name(), true); this.drvsubmenu.icon.icon_name = 'drive-harddisk-usb-symbolic'; this.menu.addMenuItem(this.drvsubmenu); //log('Drive added',drive.get_name()) //this.drvsubmenu = new PopupMenu.PopupSubMenuMenuItem(_(drive.get_name()), true); //this.drvsubmenu.icon.icon_name = 'drive-harddisk-usb-symbolic'; //this.drvsubmenu.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); }, _addVolume: function(volume) { volume.enumerate_identifiers(); var vol = new Object() log(volume.get_identifier('uuid')) log(volume.get_identifier('unix-device')) log(volume.get_identifier('class')) log(volume.get_identifier('label')) vol['volume'] = volume; vol['uuid'] = volume.get_identifier('uuid'); vol['u_device'] = volume.get_identifier('unix-device'); vol['vclass'] = volume.get_identifier('class'); vol['label'] = volume.get_identifier('label'); return vol }, _DriveRemoved: function(drive) { var me = this.menu._getMenuItems(); this._removeItemByLabel(this.menu, drive.get_name()); //this.drvsubmenu.destroy(); }, _VolumeRemoved: function(volume) { //Volume is removed, after it's mounted from system(d) log('VOLUME REMOVED',volume.get_name()) }, _VolumeAdded: function(volume) { //Volume is added, after it's unmounted from system(d) log('VOLUME CONNECTED',volume.get_name()) log('ID',this._addedVolumeId) if (volume.get_drive() == null) return var drive = volume.get_drive() if ( !volume.can_mount() || !drive.is_removable()) return log(volume.get_mount()) log(volume.enumerate_identifiers()) log(volume.get_identifier('uuid')) log(volume.get_identifier('unix-device')) log(volume.get_identifier('class')) log(volume.get_identifier('label')) var dr = volume.get_drive() log(dr.get_name()) log(dr.enumerate_identifiers()) log(dr.get_identifier('unix-device')) var me try { me = this.drvsubmenu.menu._getMenuItems(); } catch(e) { this._DriveAdded(drive) } me = this.drvsubmenu.menu._getMenuItems(); var menuItem = new VolMenuItem(volume, false); var VF = Gio.File.new_for_path('/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules'); if (VF.query_exists(null)) { log('UDEV exists','/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules') menuItem.setToggleState(true); } else { log('UDEV not exists','/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules') menuItem.setToggleState(false); } //this._removeItemByLabel(this.drvsubmenu.menu, volume.get_name()); this.drvsubmenu.menu.addMenuItem(menuItem); var connID = menuItem.connect('toggled', Lang.bind(this, function() { log('ACTIVE?',menuItem.state,menuItem.label.text) if (menuItem.state) { reg = 'register' } else { reg = 'unregister' } GLib.spawn_command_line_async( this._getCommand('mkbackup-'+reg+'@'+volume.get_identifier('unix-device')+'.service', 'start', 'system')); })); log(connID,volume.get_name()) this.menu.addMenuItem(this.drvsubmenu,1); }, _removeItemByLabel: function(menu, label) { log('LAB',label) var children = menu._getMenuItems(); for (var i = 0; i < children.length; i++) { var item = children[i]; log('REM',item.label.text,label) if (item.label.text == label) log('DESTROY',item.label.text) //item.destroy(); } }, _addMount: function(mount) { var item = new MountMenuItem(mount); this._mounts.unshift(item); this.menu.addMenuItem(item, 0); }, _removeMount: function(mount) { for (var i = 0; i < this._mounts.length; i++) { var item = this._mounts[i]; if (item.mount == mount) { item.destroy(); this._mounts.splice(i, 1); return; } } log ('Removing a mount that was never added to the menu'); }, _run_command: function(COMMAND) { var output = ""; try { output = GLib.spawn_command_line_sync(COMMAND, null, null, null, null); } catch(e) { throw e; } return output[1].toString().replace(/\n$/, "") + ""; }, _showVolume: function(volume) { var drive = volume.get_drive(); var mount = volume.get_mount(); if (drive != null && drive.is_removable()){ log('SVDRIVE',drive.get_name(),drive.is_removable()) log('SVVOL',volume.get_name(),volume.get_uuid(),drive.is_removable()); } }, _showDrive: function(drive) { //extMediaName = 'external backup-drive'; log('SDDRIVE',drive.get_name(),drive.get_volumes(),drive.has_media(),drive.is_removable(),drive.has_volumes(),drive.enumerate_identifiers()); if (drive.is_removable() && drive.has_volumes()) { //extMediaName = drive.get_name(); log('SDDREMOV') drive.get_volumes().forEach(Lang.bind(this, function(volume) { if (volume.can_mount()){ log('SDVOL',volume.get_name()); if ( volume.get_mount() != null ) { var mount = volume.get_mount(); log('SDMR',mount.get_root()); } } })); } else { log(drive.is_removable(),drive.has_volumes(),drive.get_volumes()); } }, _checkMount: function(mount) { log('CHECK MOUNT '+mount.can_unmount()+' '+mount.can_eject()); log('VOLUME '+mount.get_volume()); return(mount.can_unmount() || mount.can_eject()) }, _getCommand: function(service, action, type) { var command = "systemctl" command += " " + action command += " " + service command += " --" + type if (type == "system" && (action != 'is-active' && action != 'is-enabled')) command = "pkexec --user root " + command return 'sh -c "' + command + '; exit;"' }, _refresh_panel : function() { var active = false; var volumes = [] var mounted = false; volumes.push(this.watchvolume) // TODO: find all Volumes on the drive, holding the backup and add this // volumes to the list volumes.push('home-jakob-Videos-extern.mount') volumes.push('home-media.mount') this.aout = GLib.spawn_command_line_sync( this._getCommand(this.services.join(' '), 'is-active', 'system'))[1].toString().split('\n'); //log(this.aout); active = this.aout.indexOf('active') >= 0 var vout = GLib.spawn_command_line_sync( this._getCommand(volumes.join(' '), 'is-active', 'system'))[1].toString().split('\n'); mounted = vout.indexOf('active') >= 0 this.bkpsubmenu.icon.style = (active ? "color: #ff0000;" : "color: revert;"); MainIcon.style = (mounted ? "color: #ff0000;" : "color: revert;"); MainLabel.set_text(mounted ? _("mounted") : ""); if (this.menu.isOpen) { //Menu is open var me = this.menu._getMenuItems(); me.forEach(Lang.bind(this, function(item) { //log(item.label.text) if ( item.label.text == extMediaName ) { item.setToggleState(this._check_service('mkbackup@BKP.target','active')); } })); if (this.bkpsubmenu.menu.isOpen) { //Submenu Backu-intervals open this._refresh(); } } if (this._refreshID != 0) Mainloop.source_remove(this._refreshID); this._refreshID = Mainloop.timeout_add_seconds(refreshTime, Lang.bind(this, this._refresh_panel)); GLib.Source.set_name_by_id(this._refreshID, '[gnome-shell] this._refresh_panel'); return false; }, _check_service: function(service,stat) { var [_, aout, aerr, astat] = GLib.spawn_command_line_sync( this._getCommand(service, 'is-'+stat, 'system')); return (astat == 0); }, _refresh: function() { var me = this.bkpsubmenu.menu._getMenuItems(); //log(this.services.join(' ')) var eout = GLib.spawn_command_line_sync( this._getCommand(this.services.join(' '), 'is-enabled', 'system'))[1].toString().split('\n'); //log(eout); this._entries.forEach(Lang.bind(this, function(service,index,arr) { if (! arr[index].enabled == (eout[index] == 'enabled')) arr[index].changed = true arr[index].enabled = (eout[index] == 'enabled' ? true : false) if (! arr[index].active == (this.aout[index] == 'active')) arr[index].changed = true arr[index].active = (this.aout[index] == 'active' ? true : false) })); this._entries.forEach(Lang.bind(this, function(service,index,arr) { var serviceItem me.forEach(Lang.bind(this, function(item) { if ( item.label.text == service['name'] ) { arr[index].found = true; serviceItem = item; me.splice(me.indexOf(item),1); } })); if ( arr[index].changed ) { if (arr[index].found) { //log('update',service['name']); if ( service.staticserv ) { serviceItem.setToggleState(service.active); } else { serviceItem.setToggleState(service.enabled); } } else { //log('new',service['name']); if ( service.staticserv ) { serviceItem = new PopupTargetItem(service['name'], service.active); this.bkpsubmenu.menu.addMenuItem(serviceItem); serviceItem.connect('toggled', Lang.bind(this, function() { GLib.spawn_command_line_async( this._getCommand(service['service'], (this._check_service(service.service, 'active') ? 'stop' : 'start'), service["type"])); })); } else { serviceItem = new PopupServiceItem(service['name'], service.enabled); this.bkpsubmenu.menu.addMenuItem(serviceItem); serviceItem.connect('toggled', Lang.bind(this, function() { GLib.spawn_command_line_async( this._getCommand(service['service'], (this._check_service(service.service, 'enabled') ? 'disable' : 'enable'), service["type"])); })); serviceItem.actionButton.connect('clicked', Lang.bind(this, function() { GLib.spawn_command_line_async( this._getCommand(service['service'], (this._check_service(service.service, 'active') ? 'stop' : 'start'), service["type"])); this.menu.close(); })); } } if (serviceItem.actionButton.child) { serviceItem.actionButton.child.icon_name = (service.active ? EnabledIcon : DisabledIcon); serviceItem.actionButton.child.style = (service.active ? "color: #ff0000;" : "color: revert;"); }; if (serviceItem.transferButton) { serviceItem.transferButton.style = (service.tr ? "text-decoration: revert;" : "text-decoration: line-through;"); }; //log('Changed',service.service) } //log('X',arr[index].service,arr[index].enabled,arr[index].active,arr[index].changed) arr[index].changed = false; })); this.bkpsubmenu.menu._getMenuItems().forEach(Lang.bind(this, function(item) { var mic = 0 if ( me.length > mic ) { for (var i = mic; i < me.length; i++) { if (item == me[i]) { log('DESTROY',me[i].label.text); item.destroy(); } } } })); return true; }, _loadConfig: function() { //log('LOAD CONFIG') var intervals this.services = [] var kf = new GLib.KeyFile() var obj = new Object(); this._entries = []; if(kf.load_from_file('/etc/mksnapshot.conf',GLib.KeyFileFlags.NONE)){ //intervals = kf.get_groups()[0]; this.bkpmnt = kf.get_value('DEFAULT','bkpmnt') //log('BKP',this.bkppath) kf.get_groups()[0].forEach(Lang.bind(this, function(interval) { var obj = new Object(); var i = "" if (interval === 'DEFAULT') i = 'misc' else i = interval obj.name = i +' backups'; obj.interval = interval; obj.service = "mkbackup@"+i+".service"; this.services.push(obj.service) obj.type = "system"; obj.enabled = false; obj.active = false; obj.changed = true; obj.found = false; obj.staticserv = false; try { obj.tr = (kf.get_value(interval,"transfer").toLowerCase() === "true"); } catch(err) { obj.tr = (kf.get_value("DEFAULT","transfer").toLowerCase() === "true"); } this._entries.push(obj)})); } } }); var backupManager; function init(extensionMeta) { //Convenience.initTranslations(); var theme = imports.gi.Gtk.IconTheme.get_default(); theme.append_search_path(extensionMeta.path + "/icons"); } function enable() { backupManager = new BackupManager(); } function disable() { backupManager.destroy(); }