Initial commit (Vesion 0.1)
[tablet-suite] / src / backup / pcspcbackupmanager.py
diff --git a/src/backup/pcspcbackupmanager.py b/src/backup/pcspcbackupmanager.py
new file mode 100644 (file)
index 0000000..35bcfe3
--- /dev/null
@@ -0,0 +1,475 @@
+# low_backup_manager module
+# Authors: Nicholas Alexander && Otacilio Lacerda
+# Backup_manager class:
+#    Class responsible for backup functions like creating and removing backups
+
+import os.path
+import os
+import zlib
+import pickle
+
+from PyQt4.QtCore import *
+from zipfile import *
+
+from pcsbackupparser import *
+from pcsbackupinfo import *
+from pcsbackupmanager import *
+from pcsbackupxml import *
+
+import pcsbackuputils as utils
+
+HOME = os.path.expanduser("~")
+BACKUP_FILES_PATH = os.path.join(HOME, ".pcsuite/Backup")
+DEFAULT_CONFIG_PATH = "%s/.pcsuite/config" % HOME
+BACKUPS_FILE = os.path.join(HOME, ".pcsuite/Backup/.backups")
+
+
+class PcsPcBackupManager(PcsBackupManager):
+
+    def __init__(self):
+        PcsBackupManager.__init__(self)
+        self._backupList = []
+        self.backupInProgress = False
+        self.restoreInProgress = False
+        self.copyInProgress = False
+        
+        self.myDocsPath = "Root/home/user/MyDocs/"
+        self.backupPath = ""
+        self.currentCategory = ""
+        self.totalSize = 0
+
+    def loadBackups(self):
+        # 'XX Loading the backup list available in the PC'
+        try:
+            if os.path.exists(BACKUPS_FILE):
+                file = open(BACKUPS_FILE)
+                self._backupList = pickle.load(file)
+                file.close()
+        except IOError:
+            print "IOError while loading the PC backups"
+            # FIXME
+            #raise Exception("Error while reading backups")
+            return False
+        
+        return True
+        
+    def saveBackups(self):
+        # 'XX Saving the backup list in the config file'
+        try:
+            obj = self._backupList
+            file = open(BACKUPS_FILE, "w")     
+            pickle.dump(obj, file)
+            file.close()
+        except:
+            #raise Exception("Error while saving backups")
+            return False
+        
+        return True
+
+    def getBackupList(self):
+        """Return a list with the name of all done backups. """
+        self.loadBackups()
+        return self._backupList[:]
+    
+    def createBackup(self, backup_name, path, host_ip, categories, comment=""):
+        self.backupThread = NewBackupThread(self, backup_name, path, host_ip, 
+                                            categories, comment)
+        self.backupThread.start()
+        
+
+    def renameBackup(self, backupName, newName):
+        self.loadBackups()
+
+        backupInfo = self.getBackupInfo(backupName)
+        if backupInfo != None:
+            try:
+                old = os.path.join(str(backupInfo.getPath()), str(backupName))
+                new = os.path.join(str(backupInfo.getPath()), str(newName))
+                os.rename(old, new)
+                backupInfo.setName(newName)
+            except:
+                print "Error while changing backup name"
+                return False
+        else:
+            # "Backup not found"
+            return False
+            
+        self.saveBackups()
+        return True
+    
+    def removeBackup(self, backupName):
+        """
+            Remove a backup from pc and from _backupList.
+        """
+        self.loadBackups()
+        backupInfo = self.getBackupInfo(backupName)
+        completePath = os.path.join(str(backupInfo.getPath()), 
+                                    str(backupInfo.getName()))
+        if os.path.exists(completePath):
+            utils.removePath(completePath)
+        self._backupList.remove(backupInfo)
+        self.saveBackups()
+        
+        return True
+    
+    def copyBackupToDevice(self, deviceIp, backupName, memoryStick):
+        """ Copy a backup in the PC to some memory card inside the chosen device.
+            
+            Attributes:
+            String deviceIp - String with ip address of device.
+            String backupName - Name of backup to be copied.
+            Int memoryStick - Integer representing which memory stick backup
+                                should be copied to.
+                                0 - Removable memory stick
+                                1 - Internal memory stick
+        """
+        try:
+            self.loadBackups()
+            mountPoint = os.path.join(DEVICES_POINT, "%s/Root" % deviceIp)
+            utils.mountDevice(USER_HOST, deviceIp, mountPoint)
+            
+            backup = self.getBackupInfo(backupName)
+            backupPath = os.path.join(str(backup.getPath()), str(backupName))
+            if memoryStick != 0 and memoryStick != 1:
+                return -1
+            destination = os.path.join(mountPoint, 'media/mmc%s' % 
+                                       (memoryStick+1), 'backups', backupName)
+            
+            self.setCopyInProgress(True)
+            if self.copy(backupPath, destination) == 0:
+                return 0
+            self.setCopyInProgress(False)
+        finally:
+            utils.unmountDevice(mountPoint)
+
+    
+    def getBackupInfo(self, backupName):
+        for backupInfo in self._backupList:
+            if backupInfo.getName() == backupName:
+                return backupInfo
+        return None
+    
+    def startBackupRestore(self, backupInfo, device_ip, categories):
+        return self.restoreBackup(backupInfo, device_ip, categories)
+    
+    def setCopyInProgress(self, status):
+        self.copyInProgress = status
+    
+    def setBackupInProgress(self, status):
+        self.backupInProgress = status
+        
+        
+    def setRestoreInProgress(self, status):
+        self.restoreInProgress = status
+        
+    # FIXME: rewrite this method. Add error handling, some more reliable code
+    def _runCreateBackup(self, backup_name, path, host_ip, categories, comment=""):
+        """Create a backup and add it to _backupList.
+
+        Backup all files and directories of categories chosen. The device
+        system file is mounted on PC and them files are compressed in a zip
+        format. All zip files from each category are saved in the given path.
+
+        Arguments:
+        backup_name -- Backup's name. This name will be the name of the backup
+            folder.
+        path -- Location to save Backup file
+        host_ip -- The device IP address.
+        categories -- A python dictonary where the keys are the categories from
+            osso-backup, and the value can be either True or False, to identify
+            which categories to backup.
+        comment -- Any comment about the backup. It will be saved in Backup
+            object.
+            
+        """
+           
+        backupInfo = None
+        self.setBackupInProgress(True)
+        self.currentCategory = ""
+         
+        try:
+            # Mount device folders
+            self.loadBackups()
+            mountPoint = os.path.join(DEVICES_POINT, "%s/Root" % host_ip)
+            utils.mountDevice(USER_HOST, host_ip, mountPoint)
+            
+            # Create backup folder in the given path
+            self.backupPath = os.path.join(str(path), backup_name)
+            self._createBackupFolder(self.backupPath)
+            
+            # Copying osso-backup configuration files to TabletSuite folder and
+            # parsing backup information from these files.
+            # ( Information = which paths should be in each backup category)
+            utils.copyOssoBackupConfigFiles(DEFAULT_CONFIG_PATH, mountPoint)
+            xmlParser = PcsBackupParser()
+            xmlParser.fillLocationsDict(DEFAULT_CONFIG_PATH)
+            backupItemDict = xmlParser.getLocationsDict()
+            
+            # Changing work directory to ease file search. Keeping previous
+            # folder to come back after backup process.
+            current_dir = os.getcwd()
+            workPath = DEVICES_POINT + str(host_ip)
+            os.chdir(workPath)
+
+            # Computing total size that this backup will have.
+            self.totalSize = self._getTotalSize(backupItemDict, categories)
+            
+            
+            # Start backup process. Create backup of paths for each locations
+            # category
+            self.backedUpNumber = 0
+            filesByCategory = {}
+            for category in backupItemDict.keys():
+                self.currentCategory = category
+                
+                for backupItem in backupItemDict[category]:
+                    self._emitProgress()
+                    # If current backupInfo category is valid and it's value is 
+                    # True in categories dictionary, add it to current backup. 
+                    if (category in categories) and (categories[category]):
+                        categoryFilesNumber = 0
+                        categoryFile = os.path.join(self.backupPath, 
+                                                    category + '.zip')
+
+                        if os.path.exists(categoryFile):
+                            zipfile = utils.openZip(categoryFile, 'a')
+                        else:
+                            zipfile = utils.openZip(categoryFile, 'w')
+
+                        objPath = backupItem.path.replace('/','Root/',1)
+                        backupFolder = os.path.join(workPath, objPath)
+                        categoryFilesNumber = self.zipFolder(backupFolder,
+                                                              zipfile, objPath,
+                                                               category)
+                        if categoryFilesNumber == -1:
+                            os.chdir(current_dir)
+                            return 0
+                        self.backedUpNumber += categoryFilesNumber
+                        self._emitProgress()
+                        
+                        # Update the filesByCategory dictionary with the 
+                        # current number of files from current category that 
+                        # were already copied.
+                        self._updateCategoryCount(filesByCategory, category, 
+                                                  categoryFilesNumber)
+                        utils.closeZip(zipfile)
+                        # If category had no file to copy remove its folder
+                        if int(utils.getSize(categoryFile) == 0):
+                            os.remove(categoryFile)
+            
+                
+            # Copying media files from device user folder if media category is
+            # True in categories dictionary.
+            if ("media" in categories) and (categories["media"]):
+                self.currentCategory = "media"
+                result = self._backupMedias(workPath)
+                if result == -1:
+                    os.chdir(current_dir)
+                    return 0
+                # Update backup files number count
+                self.backedUpNumber += result
+                self._updateCategoryCount(filesByCategory, "media", result)
+                zipPath = os.path.join(self.backupPath,'media.zip')
+                if int(utils.getSize(zipPath) == 0):
+                    os.remove(zipPath)
+            
+            
+            # Copying documents files from device user folder if documents
+            # category is True in categories dictionary.
+            if ("documents" in categories) and (categories["documents"]):
+                self.currentCategory = "documents"
+                result = self._backupDocuments(workPath)
+                # Update backup files number count
+                self.backedUpNumber += result
+                if result == -1:
+                    os.chdir(current_dir)
+                    return 0
+                self._updateCategoryCount(filesByCategory, "documents", 
+                                          result)
+                zipPath = os.path.join(self.backupPath, 'documents.zip')
+                if int(utils.getSize(zipPath) == 0):
+                    os.remove(zipPath)          
+                
+                
+            # Change to previous work directory
+            os.chdir(current_dir)
+           
+            if self.backedUpNumber == 0:
+                utils.removePath(self.backupPath)
+                return -1
+            
+            # Create Backup Object, add it to this manager backup list and save
+            # the list in the backup file.
+            backup_size = utils.getSize(self.backupPath)
+            backupInfo = PcsBackupInfo(backup_name, path, backup_size, comment)
+            backupInfo.setFilesNumber(self.backedUpNumber)
+            self._backupList.append(backupInfo)
+            self.saveBackups()
+            
+            createXml(backupInfo, filesByCategory, host_ip)
+            self.setBackupInProgress(False)
+        finally:
+            utils.unmountDevice(mountPoint)
+            
+        return backupInfo
+    
+    
+    def _backupDocuments(self, workPath):
+        """ Create backup of documents files in user folder. """
+        categoryFilesNumber = 0
+        destinationPath = os.path.join(self.backupPath,'documents.zip')
+        if os.path.exists(destinationPath):
+            zipfile = utils.openZip(destinationPath, 'a')
+        else:
+            zipfile = utils.openZip(destinationPath, 'w')
+            
+        docsPath = os.path.join(self.myDocsPath, ".documents")
+        
+        if os.path.exists(docsPath):
+            backupPath = os.path.join(workPath, docsPath)
+            categoryFilesNumber = self.zipFolder(backupPath, zipfile, docsPath,
+                                                 "documents")
+            
+        utils.closeZip(zipfile)
+        return categoryFilesNumber
+           
+                    
+    def _backupMedias(self, workPath):
+        """ Create backup of media files in user folder. """
+        categoryFilesNumber = 0
+        destinationPath = os.path.join(self.backupPath,'media.zip')
+        if os.path.exists(destinationPath):
+            zipfile = utils.openZip(destinationPath, 'a')
+        else:
+            zipfile = utils.openZip(destinationPath, 'w')
+            
+        userFilesPath = self.myDocsPath
+        if os.path.exists(os.path.join(userFilesPath)):
+            for folder in os.listdir(userFilesPath):
+                if folder != '.documents':
+                    objPath = os.path.join(userFilesPath, folder)
+                    backupDir = os.path.join(workPath, objPath)
+                    result = self.zipFolder(backupDir, zipfile,
+                                                         objPath, "media")
+                    if result != -1:
+                        categoryFilesNumber += result
+                    else:
+                        return result
+                    
+        utils.closeZip(zipfile)
+        return categoryFilesNumber
+    
+                    
+    def _createBackupFolder(self, newBackupPath):
+        if not utils.createFolder(newBackupPath):
+            return False
+    
+    
+    def _emitProgress(self):
+        currentSize = utils.getSize(self.backupPath) 
+        percentage = self.computePercentage(self.totalSize, currentSize)
+        self.emit(SIGNAL("backupProgress"), (percentage, self.currentCategory))
+    
+    
+    def _getTotalSize(self, backupFileDict, categories):
+        size = 0
+        for category in backupFileDict.keys():
+            for backupItem in backupFileDict[category]:
+                if (category in categories) and (categories[category]):
+                    objPath = backupItem.path.replace('/','Root/',1)
+                    size += utils.getSize(objPath)
+        
+        if categories["documents"]:
+            size += utils.getSize(os.path.join(self.myDocsPath,
+                                                     ".documents"))
+        if categories["media"]:
+            for folder in os.listdir(self.myDocsPath):                
+                if folder != '.documents':
+                    objPath = os.path.join(self.myDocsPath, folder)
+                    size += utils.getSize(objPath)
+        return size
+    
+    def _updateCategoryCount(self, filesByCategory, category, backed_up_number):
+        if str(category) not in filesByCategory.keys():
+            filesByCategory[category] = backed_up_number
+        else:
+            filesByCategory[category] += backed_up_number
+    
+            
+    def _verify_backup_name(self, backup_name):
+        """ Check if already exists any backup with the given name inside the
+        pc backup list. In case there is one, this function will return another
+        name to be set as the new backup name, else it will return the same 
+        name that was given.
+        
+        """
+        if self.getBackupInfo(backup_name) != None:
+            counter = 1
+            new_name = backup_name + "%02d" % counter
+            while(self.getBackupInfo(new_name)) != None:
+                counter += 1
+                new_name = backup_name + "%02d" % counter
+            
+            backup_name = new_name
+        return backup_name
+    
+    def zipFolder(self, path, zipfile, obj_path, category):
+        # Compress the folder from the given path, with all directories and
+        #   files inside it
+        # zipfile: The ZipFile object to append the folder
+        # obj_path: The ZipFile path
+        count = 0
+        self._emitProgress()
+        if os.path.exists(path):
+            if os.path.isdir(path):
+                files = os.listdir(path)
+                for node in files:
+                    if os.path.isdir(os.path.join(path, node)):
+                        zipCount = self.zipFolder(os.path.join(path, node), zipfile,
+                                            os.path.join(obj_path, node), category)
+                        if zipCount == -1:
+                            return -1
+                        else:
+                            count += zipCount
+                    else:
+                        # Check if backup was canceled and return -1 case positive
+                        if not self.backupInProgress:
+                            utils.removePath(self.backupPath)
+                            return -1
+
+                        if utils.zip(zipfile, os.path.join(obj_path, node)):
+                            count += 1
+                            self._emitProgress()
+            else:
+                # Check if backup was canceled and return -1 case positive
+                if not self.backupInProgress:
+                    utils.removePath(self.backupPath)
+                    return -1
+                
+                if utils.zip(zipfile, obj_path):
+                    count += 1
+                    self._emitProgress()
+        return count
+    
+
+
+class NewBackupThread(QThread):
+    def __init__(self, manager, backupName, path, hostIp, categories, comment):
+        QThread.__init__(self)
+        
+        self.manager = manager
+        self.backupName = backupName
+        self.path = path
+        self.hostIp = hostIp
+        self.categories = categories
+        self.comment = comment
+    
+    def run(self):
+        ret = self.manager._runCreateBackup (self.backupName, self.path,
+                                             self.hostIp, self.categories,
+                                             str(self.comment))
+        self.manager.emit(SIGNAL("backupDone"), ret, 
+                          (self.manager.totalSize, self.manager.backedUpNumber))    
+
+
+        
\ No newline at end of file