diff --git a/backupdlg.ui b/backupdlg.ui
index 04cb139..522ce78 100644
--- a/backupdlg.ui
+++ b/backupdlg.ui
@@ -19,7 +19,7 @@
QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage
-
+
-
@@ -39,7 +39,7 @@
-
+
-
@@ -55,7 +55,7 @@
-
-
+
0
@@ -92,10 +92,10 @@
-
-
+
-
-
+
...
@@ -106,7 +106,7 @@
-
+
-
@@ -157,7 +157,7 @@ p, li { white-space: pre-wrap; }
-
+
-
@@ -176,7 +176,7 @@ p, li { white-space: pre-wrap; }
-
-
+
24
diff --git a/icons/mount.png b/icons/mount.png
new file mode 100644
index 0000000..8c84158
Binary files /dev/null and b/icons/mount.png differ
diff --git a/icons/redfirewall2.png b/icons/redfirewall2.png
new file mode 100644
index 0000000..e611d89
Binary files /dev/null and b/icons/redfirewall2.png differ
diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py
index 5544ce9..40ece49 100755
--- a/qubesmanager/appmenu_select.py
+++ b/qubesmanager/appmenu_select.py
@@ -39,9 +39,10 @@ from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, Pro
import subprocess
import time
-import threading
+
from operator import itemgetter
+from thread_monitor import *
from multiselectwidget import *
whitelisted_filename = 'whitelisted-appmenus.list'
@@ -51,22 +52,6 @@ class AppListWidgetItem(QListWidgetItem):
super(AppListWidgetItem, self).__init__(name, parent)
self.filename = filename
-class ThreadMonitor(QObject):
- def __init__(self):
- self.success = True
- self.error_msg = None
- self.event_finished = threading.Event()
-
- def set_error_msg(self, error_msg):
- self.success = False
- self.error_msg = error_msg
- self.set_finished()
-
- def is_finished(self):
- return self.event_finished.is_set()
-
- def set_finished(self):
- self.event_finished.set()
class AppmenuSelectManager:
diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py
index 8d223f1..5150cc2 100644
--- a/qubesmanager/backup.py
+++ b/qubesmanager/backup.py
@@ -30,6 +30,7 @@ from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesException
from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost
+from qubes import qubesutils
import qubesmanager.resources_rc
@@ -37,9 +38,12 @@ from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, Pro
import subprocess
import time
-import threading
+from thread_monitor import *
from operator import itemgetter
+from datetime import datetime
+from string import replace
+
from ui_backupdlg import *
from multiselectwidget import *
@@ -47,40 +51,255 @@ from multiselectwidget import *
class BackupVMsWindow(Ui_Backup, QWizard):
- def __init__(self, parent=None):
+ __pyqtSignals__ = ("backup_progress(int)",)
+
+ excluded = []
+ to_backup = []
+
+ def __init__(self, app, qvm_collection, blk_manager, parent=None):
super(BackupVMsWindow, self).__init__(parent)
+ self.app = app
+ self.qvm_collection = qvm_collection
+ self.blk_manager = blk_manager
+
+ self.backup_dir = None
+ self.func_output = []
+
+ for vm in self.qvm_collection.values():
+ if vm.qid == 0:
+ self.vm = vm
+ break;
+
+ assert self.vm != None
+
self.setupUi(self)
- self.selectVMsWidget = MultiSelectWidget(self)
- self.verticalLayout.insertWidget(1, self.selectVMsWidget)
+ self.dir_line_edit.setReadOnly(True)
+
+ self.select_vms_widget = MultiSelectWidget(self)
+ self.verticalLayout.insertWidget(1, self.select_vms_widget)
- self.selectVMsWidget.available_list.addItem("netVM1")
- self.selectVMsWidget.available_list.addItem("appVM1")
- self.selectVMsWidget.available_list.addItem("appVM2")
- self.selectVMsWidget.available_list.addItem("templateVM1")
-
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
+ self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
+ self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
+ self.select_vms_page.isComplete = self.has_selected_vms
+ self.select_dir_page.isComplete = self.has_selected_dir
+ #FIXME
+ #this causes to run isComplete() twice, I don't know why
+ self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()"))
-
- def reject(self):
- self.done(0)
+ self.__fill_vms_list__()
+ self.__fill_devs_list__()
- def save_and_apply(self):
- pass
-
- @pyqtSlot(name='on_selectPathButton_clicked')
- def selectPathButton_clicked(self):
- self.path = self.pathLineEdit.text()
- newPath = QFileDialog.getExistingDirectory(self, 'Select backup directory.')
- if newPath:
- self.pathLineEdit.setText(newPath)
- self.path = newPath
-
- def current_page_changed(self, id):
- self.button(self.CancelButton).setDisabled(id==3)
+ def __fill_vms_list__(self):
+ for vm in self.qvm_collection.values():
+ if vm.is_running() and vm.qid != 0:
+ self.excluded.append(vm.name)
+ continue
+ if vm.is_appvm() and vm.internal:
+ self.excluded.append(vm.name)
+ continue
+
+ if vm.is_template() and vm.installed_by_rpm:
+ self.excluded.append(vm.name)
+ continue
+
+ self.to_backup.append(vm.name)
+ self.select_vms_widget.available_list.addItem(vm.name)
+
+ def __fill_devs_list__(self):
+ self.dev_combobox.clear()
+ self.dev_combobox.addItem("None")
+ for a in self.blk_manager.attached_devs:
+ if self.blk_manager.attached_devs[a]['attached_to']['vm'] == self.vm.name :
+ att = a + " " + unicode(self.blk_manager.attached_devs[a]['size']) + " " + self.blk_manager.attached_devs[a]['desc']
+ self.dev_combobox.addItem(att, QVariant(a))
+ for a in self.blk_manager.free_devs:
+ att = a + " " + unicode(self.blk_manager.free_devs[a]['size']) + " " + self.blk_manager.free_devs[a]['desc']
+ self.dev_combobox.addItem(att, QVariant(a))
+ self.dev_combobox.setCurrentIndex(0) #current selected is null ""
+ self.prev_dev_idx = 0
+ self.dir_line_edit.clear()
+ self.dir_line_edit.setEnabled(False)
+ self.select_path_button.setEnabled(False)
+
+ def __check_if_mounted__(self, dev_path):
+ mounts_file = open("/proc/mounts")
+ for m in list(mounts_file):
+ if m.startswith(dev_path):
+ print m
+ return m.split(" ")[1]
+ return None
+
+ def __mount_device__(self, dev_path):
+ try:
+ mount_dir_name = "backup" + replace(str(datetime.now()),' ', '-').split(".")[0]
+ pmount_cmd = ["pmount", dev_path, mount_dir_name]
+ res = subprocess.check_call(pmount_cmd)
+ print "pmount device res: ", res
+ except Exception as ex:
+ QMessageBox.warning (None, "Error mounting selected device!", "ERROR: {0}".format(ex))
+ return None
+ if res == 0:
+ self.dev_mount_path = "/media/"+mount_dir_name
+ return self.dev_mount_path
+
+ def __umount_device__(self, dev_mount_path):
+ try:
+ pumount_cmd = ["pumount", dev_mount_path]
+ res = subprocess.check_call(pumount_cmd)
+ print "pumount device res: ", res
+ except Exception as ex:
+ QMessageBox.warning (None, "Could not unmount backup device!", "ERROR: {0}".format(ex))
+
+
+
+ def __enable_dir_line_edit__(self, boolean):
+ self.dir_line_edit.setEnabled(boolean)
+ self.select_path_button.setEnabled(boolean)
+
+
+ def dev_combobox_activated(self, idx):
+ print self.dev_combobox.currentText()
+ if idx == self.prev_dev_idx: #nothing has changed
+ return
+ #there was a change
+ self.prev_dev_idx = idx
+
+ self.dir_line_edit.setText("")
+ self.backup_dir = None
+ self.dev_mount_path = None
+ self.__enable_dir_line_edit__(False)
+
+ if self.dev_combobox.currentText() != "None": #An existing device chosen
+ dev_name = str(self.dev_combobox.itemData(idx).toString())
+
+ if dev_name in self.blk_manager.free_devs:
+ if dev_name.startswith(self.vm.name): # originally attached to dom0
+ dev_path = "/dev/"+dev_name.split(":")[1]
+ print "device from dom0 - no need to attach"
+
+ else: # originally attached to another domain, eg. usbvm
+ print "device from " + dev_name.split(":")[0]
+ #attach it to dom0, then treat it as an attached device
+ self.blk_manager.attach_device(self.vm, dev_name)
+
+ if dev_name in self.blk_manager.attached_devs: #is attached to dom0
+ print "device attached as " + self.blk_manager.attached_devs[dev_name]['attached_to']['frontend']
+ assert self.blk_manager.attached_devs[dev_name]['attached_to']['vm'] == self.vm.name
+
+ dev_path = "/dev/" + self.blk_manager.attached_devs[dev_name]['attached_to']['frontend']
+
+ #check if device mounted
+ self.dev_mount_path = self.__check_if_mounted__(dev_path)
+ if self.dev_mount_path != None:
+ self.__enable_dir_line_edit__(True)
+ else:
+ self.dev_mount_path = self.__mount_device__(dev_path)
+ if self.dev_mount_path != None:
+ self.__enable_dir_line_edit__(True)
+
+ self.select_dir_page.emit(SIGNAL("completeChanged()"))
+
+
+
+ @pyqtSlot(name='on_select_path_button_clicked')
+ def select_path_button_clicked(self):
+ self.backup_dir = self.dir_line_edit.text()
+ file_dialog = QFileDialog()
+ file_dialog.setReadOnly(True)
+ new_path = file_dialog.getExistingDirectory(self, "Select backup directory.", self.dev_mount_path)
+ if new_path:
+ self.dir_line_edit.setText(new_path)
+ self.backup_dir = new_path
+ self.select_dir_page.emit(SIGNAL("completeChanged()"))
+
+ def validateCurrentPage(self):
+ if self.currentPage() is self.select_vms_page:
+ for i in range(self.select_vms_widget.available_list.count()):
+ vmname = self.select_vms_widget.available_list.item(i).text()
+ self.excluded.append(vmname)
+ return True
+
+ def gather_output(self, s):
+ self.func_output.append(s)
+
+ def update_progress_bar(self, value):
+ print "progress bar value: ", value
+ self.emit(SIGNAL("backup_progress(int)"), value)
+
+
+ def __do_backup__(self, thread_monitor):
+ print "doiing backup"
+ msg = []
+ try:
+ qubesutils.backup_do(str(self.backup_dir), self.files_to_backup, self.update_progress_bar)
+ #simulate_long_lasting_proces(10, self.update_progress_bar)
+ except Exception as ex:
+ print "got exception from backup"
+ msg.append(str(ex))
+
+ if len(msg) > 0 :
+ thread_monitor.set_error_msg('\n'.join(msg))
+
+ thread_monitor.set_finished()
+
+
+ def current_page_changed(self, id):
+ if self.currentPage() is self.confirm_page:
+ del self.func_output[:]
+ self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output)
+ for i in self.excluded:
+ print i
+ self.textEdit.setReadOnly(True)
+ self.textEdit.setFontFamily("Monospace")
+ self.textEdit.setText("\n".join(self.func_output))
+ for i in self.func_output:
+ print i
+
+ for s in self.files_to_backup:
+ print s
+
+ elif self.currentPage() is self.commit_page:
+ self.button(self.CancelButton).setDisabled(True)
+ self.button(self.FinishButton).setDisabled(True)
+ print "butons disabled"
+ self.thread_monitor = ThreadMonitor()
+ thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
+ thread.daemon = True
+ print "will start thread"
+ thread.start()
+
+ while not self.thread_monitor.is_finished():
+ self.app.processEvents()
+ time.sleep (0.1)
+
+ if not self.thread_monitor.success:
+ QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
+
+ self.__umount_device__(self.dev_mount_path)
+ self.button(self.FinishButton).setEnabled(True)
+
+
+ def has_selected_vms(self):
+ print "isComplete called"
+ return self.select_vms_widget.selected_list.count() > 0
+
+ def has_selected_dir(self):
+ return self.backup_dir != None
+
+
+def simulate_long_lasting_proces(period, progress_callback):
+ for i in range(period):
+ progress_callback((i*100)/period)
+ time.sleep(1)
+
+ progress_callback(100)
+ return 0
+
# Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet
@@ -113,7 +332,7 @@ def main():
app = QApplication(sys.argv)
app.setOrganizationName("The Qubes Project")
app.setOrganizationDomain("http://qubes-os.org")
- app.setApplicationName("Qubes Restore VMs")
+ app.setApplicationName("Qubes Backup VMs")
sys.excepthook = handle_exception
diff --git a/qubesmanager/main.py b/qubesmanager/main.py
index bfbc1e4..9e666d1 100755
--- a/qubesmanager/main.py
+++ b/qubesmanager/main.py
@@ -42,12 +42,12 @@ from settings import VMSettingsWindow
from restore import RestoreVMsWindow
from backup import BackupVMsWindow
from global_settings import GlobalSettingsWindow
+from thread_monitor import *
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess
import time
-import threading
from datetime import datetime,timedelta
updates_stat_file = 'last_update.stat'
@@ -486,23 +486,6 @@ class VmShutdownMonitor(QObject):
else:
QTimer.singleShot (vm_shutdown_timeout, self.check_if_vm_has_shutdown)
-class ThreadMonitor(QObject):
- def __init__(self):
- self.success = True
- self.error_msg = None
- self.event_finished = threading.Event()
-
- def set_error_msg(self, error_msg):
- self.success = False
- self.error_msg = error_msg
- self.set_finished()
-
- def is_finished(self):
- return self.event_finished.is_set()
-
- def set_finished(self):
- self.event_finished.set()
-
class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
row_height = 30
@@ -1105,7 +1088,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
@pyqtSlot(name='on_action_backup_triggered')
def action_backup_triggered(self):
- backup_window = BackupVMsWindow()
+ backup_window = BackupVMsWindow(app, self.qvm_collection, self.blk_manager)
backup_window.exec_()
@@ -1151,8 +1134,10 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow):
action = self.blk_menu.addAction(QIcon(":/remove.png"), str)
action.setData(QVariant(d))
- if self.blk_menu.isEmpty() and len(self.blk_manager.free_devs) > 0:
+ if len(self.blk_manager.free_devs) > 0:
for d in self.blk_manager.free_devs:
+ if d.startswith(vm.name):
+ continue
str = "Attach " + d + " " + unicode(self.blk_manager.free_devs[d]['size']) + " " + self.blk_manager.free_devs[d]['desc']
action = self.blk_menu.addAction(QIcon(":/add.png"), str)
action.setData(QVariant(d))
diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py
index fbedf72..a9de974 100644
--- a/qubesmanager/multiselectwidget.py
+++ b/qubesmanager/multiselectwidget.py
@@ -5,6 +5,8 @@ from ui_multiselectwidget import *
class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
+ __pyqtSignals__ = ("selected_changed()",)
+
def __init__(self, parent=None):
super(MultiSelectWidget, self).__init__()
self.setupUi(self);
@@ -23,18 +25,20 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
item = src.takeItem(row)
dst.addItem(item)
dst.sortItems()
+ self.emit(SIGNAL("selected_changed()"))
def add_selected(self):
self.switch_selected(self.available_list, self.selected_list)
def remove_selected(self):
- self.switch_selected(self.selected_list, self.available_list)
-
+ self.switch_selected(self.selected_list, self.available_list)
+
def move_all(self, src, dst):
while src.count() > 0:
item = src.takeItem(0)
dst.addItem(item)
dst.sortItems()
+ self.emit(SIGNAL("selected_changed()"))
def add_all(self):
self.move_all(self.available_list, self.selected_list)
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
index ef9a1a7..9ca2475 100644
--- a/qubesmanager/settings.py
+++ b/qubesmanager/settings.py
@@ -145,7 +145,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
thread_monitor.set_finished()
return
#self.fw_model.apply_rules()
- #self.AppListManager.save_appmenu_select_changes()
+ self.AppListManager.save_appmenu_select_changes()
thread_monitor.set_finished()
def current_tab_changed(self, idx):
diff --git a/qubesmanager/thread_monitor.py b/qubesmanager/thread_monitor.py
new file mode 100644
index 0000000..4d5e915
--- /dev/null
+++ b/qubesmanager/thread_monitor.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python2.6
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2011 Marek Marczykowski
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#
+
+
+from PyQt4.QtCore import *
+
+import threading
+
+class ThreadMonitor(QObject):
+ def __init__(self):
+ self.success = True
+ self.error_msg = None
+ self.event_finished = threading.Event()
+
+ def set_error_msg(self, error_msg):
+ self.success = False
+ self.error_msg = error_msg
+ self.set_finished()
+
+ def is_finished(self):
+ return self.event_finished.is_set()
+
+ def set_finished(self):
+ self.event_finished.set()
+
diff --git a/restoredlg.ui b/restoredlg.ui
index 8ff2cee..9c9d244 100644
--- a/restoredlg.ui
+++ b/restoredlg.ui
@@ -19,7 +19,7 @@
QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage
-
+
-
@@ -111,7 +111,7 @@
-
+
-
@@ -184,7 +184,7 @@
-
+
-
@@ -235,7 +235,7 @@ p, li { white-space: pre-wrap; }
-
+
-