Initial commit (Vesion 0.1)
[tablet-suite] / src / backup / .svn / text-base / pcspcbackupmanager.py.svn-base
1 # low_backup_manager module
2 # Authors: Nicholas Alexander && Otacilio Lacerda
3 # Backup_manager class:
4 #    Class responsible for backup functions like creating and removing backups
5
6 import os.path
7 import os
8 import zlib
9 import pickle
10
11 from PyQt4.QtCore import *
12 from zipfile import *
13
14 from pcsbackupparser import *
15 from pcsbackupinfo import *
16 from pcsbackupmanager import *
17 from pcsbackupxml import *
18
19 import pcsbackuputils as utils
20
21 HOME = os.path.expanduser("~")
22 BACKUP_FILES_PATH = os.path.join(HOME, ".pcsuite/Backup")
23 DEFAULT_CONFIG_PATH = "%s/.pcsuite/config" % HOME
24 BACKUPS_FILE = os.path.join(HOME, ".pcsuite/Backup/.backups")
25
26
27 class PcsPcBackupManager(PcsBackupManager):
28
29     def __init__(self):
30         PcsBackupManager.__init__(self)
31         self._backupList = []
32         self.backupInProgress = False
33         self.restoreInProgress = False
34         self.copyInProgress = False
35         
36         self.myDocsPath = "Root/home/user/MyDocs/"
37         self.backupPath = ""
38         self.currentCategory = ""
39         self.totalSize = 0
40
41     def loadBackups(self):
42         # 'XX Loading the backup list available in the PC'
43         try:
44             if os.path.exists(BACKUPS_FILE):
45                 file = open(BACKUPS_FILE)
46                 self._backupList = pickle.load(file)
47                 file.close()
48         except IOError:
49             print "IOError while loading the PC backups"
50             # FIXME
51             #raise Exception("Error while reading backups")
52             return False
53         
54         return True
55         
56     def saveBackups(self):
57         # 'XX Saving the backup list in the config file'
58         try:
59             obj = self._backupList
60             file = open(BACKUPS_FILE, "w")     
61             pickle.dump(obj, file)
62             file.close()
63         except:
64             #raise Exception("Error while saving backups")
65             return False
66         
67         return True
68
69     def getBackupList(self):
70         """Return a list with the name of all done backups. """
71         self.loadBackups()
72         return self._backupList[:]
73     
74     def createBackup(self, backup_name, path, host_ip, categories, comment=""):
75         self.backupThread = NewBackupThread(self, backup_name, path, host_ip, 
76                                             categories, comment)
77         self.backupThread.start()
78         
79
80     def renameBackup(self, backupName, newName):
81         self.loadBackups()
82
83         backupInfo = self.getBackupInfo(backupName)
84         if backupInfo != None:
85             try:
86                 old = os.path.join(str(backupInfo.getPath()), str(backupName))
87                 new = os.path.join(str(backupInfo.getPath()), str(newName))
88                 os.rename(old, new)
89                 backupInfo.setName(newName)
90             except:
91                 print "Error while changing backup name"
92                 return False
93         else:
94             # "Backup not found"
95             return False
96             
97         self.saveBackups()
98         return True
99     
100     def removeBackup(self, backupName):
101         """
102             Remove a backup from pc and from _backupList.
103         """
104         self.loadBackups()
105         backupInfo = self.getBackupInfo(backupName)
106         completePath = os.path.join(str(backupInfo.getPath()), 
107                                     str(backupInfo.getName()))
108         if os.path.exists(completePath):
109             utils.removePath(completePath)
110         self._backupList.remove(backupInfo)
111         self.saveBackups()
112         
113         return True
114     
115     def copyBackupToDevice(self, deviceIp, backupName, memoryStick):
116         """ Copy a backup in the PC to some memory card inside the chosen device.
117             
118             Attributes:
119             String deviceIp - String with ip address of device.
120             String backupName - Name of backup to be copied.
121             Int memoryStick - Integer representing which memory stick backup
122                                 should be copied to.
123                                 0 - Removable memory stick
124                                 1 - Internal memory stick
125         """
126         try:
127             self.loadBackups()
128             mountPoint = os.path.join(DEVICES_POINT, "%s/Root" % deviceIp)
129             utils.mountDevice(USER_HOST, deviceIp, mountPoint)
130             
131             backup = self.getBackupInfo(backupName)
132             backupPath = os.path.join(str(backup.getPath()), str(backupName))
133             if memoryStick != 0 and memoryStick != 1:
134                 return -1
135             destination = os.path.join(mountPoint, 'media/mmc%s' % 
136                                        (memoryStick+1), 'backups', backupName)
137             
138             self.setCopyInProgress(True)
139             if self.copy(backupPath, destination) == 0:
140                 return 0
141             self.setCopyInProgress(False)
142         finally:
143             utils.unmountDevice(mountPoint)
144
145     
146     def getBackupInfo(self, backupName):
147         for backupInfo in self._backupList:
148             if backupInfo.getName() == backupName:
149                 return backupInfo
150         return None
151     
152     def startBackupRestore(self, backupInfo, device_ip, categories):
153         return self.restoreBackup(backupInfo, device_ip, categories)
154     
155     def setCopyInProgress(self, status):
156         self.copyInProgress = status
157     
158     def setBackupInProgress(self, status):
159         self.backupInProgress = status
160         
161         
162     def setRestoreInProgress(self, status):
163         self.restoreInProgress = status
164         
165     # FIXME: rewrite this method. Add error handling, some more reliable code
166     def _runCreateBackup(self, backup_name, path, host_ip, categories, comment=""):
167         """Create a backup and add it to _backupList.
168
169         Backup all files and directories of categories chosen. The device
170         system file is mounted on PC and them files are compressed in a zip
171         format. All zip files from each category are saved in the given path.
172
173         Arguments:
174         backup_name -- Backup's name. This name will be the name of the backup
175             folder.
176         path -- Location to save Backup file
177         host_ip -- The device IP address.
178         categories -- A python dictonary where the keys are the categories from
179             osso-backup, and the value can be either True or False, to identify
180             which categories to backup.
181         comment -- Any comment about the backup. It will be saved in Backup
182             object.
183             
184         """
185            
186         backupInfo = None
187         self.setBackupInProgress(True)
188         self.currentCategory = ""
189          
190         try:
191             # Mount device folders
192             self.loadBackups()
193             mountPoint = os.path.join(DEVICES_POINT, "%s/Root" % host_ip)
194             utils.mountDevice(USER_HOST, host_ip, mountPoint)
195             
196             # Create backup folder in the given path
197             self.backupPath = os.path.join(str(path), backup_name)
198             self._createBackupFolder(self.backupPath)
199             
200             # Copying osso-backup configuration files to TabletSuite folder and
201             # parsing backup information from these files.
202             # ( Information = which paths should be in each backup category)
203             utils.copyOssoBackupConfigFiles(DEFAULT_CONFIG_PATH, mountPoint)
204             xmlParser = PcsBackupParser()
205             xmlParser.fillLocationsDict(DEFAULT_CONFIG_PATH)
206             backupItemDict = xmlParser.getLocationsDict()
207             
208             # Changing work directory to ease file search. Keeping previous
209             # folder to come back after backup process.
210             current_dir = os.getcwd()
211             workPath = DEVICES_POINT + str(host_ip)
212             os.chdir(workPath)
213
214             # Computing total size that this backup will have.
215             self.totalSize = self._getTotalSize(backupItemDict, categories)
216             
217             
218             # Start backup process. Create backup of paths for each locations
219             # category
220             self.backedUpNumber = 0
221             filesByCategory = {}
222             for category in backupItemDict.keys():
223                 self.currentCategory = category
224                 
225                 for backupItem in backupItemDict[category]:
226                     self._emitProgress()
227                     # If current backupInfo category is valid and it's value is 
228                     # True in categories dictionary, add it to current backup. 
229                     if (category in categories) and (categories[category]):
230                         categoryFilesNumber = 0
231                         categoryFile = os.path.join(self.backupPath, 
232                                                     category + '.zip')
233
234                         if os.path.exists(categoryFile):
235                             zipfile = utils.openZip(categoryFile, 'a')
236                         else:
237                             zipfile = utils.openZip(categoryFile, 'w')
238
239                         objPath = backupItem.path.replace('/','Root/',1)
240                         backupFolder = os.path.join(workPath, objPath)
241                         categoryFilesNumber = self.zipFolder(backupFolder,
242                                                               zipfile, objPath,
243                                                                category)
244                         if categoryFilesNumber == -1:
245                             os.chdir(current_dir)
246                             return 0
247                         self.backedUpNumber += categoryFilesNumber
248                         self._emitProgress()
249                         
250                         # Update the filesByCategory dictionary with the 
251                         # current number of files from current category that 
252                         # were already copied.
253                         self._updateCategoryCount(filesByCategory, category, 
254                                                   categoryFilesNumber)
255                         utils.closeZip(zipfile)
256                         # If category had no file to copy remove its folder
257                         if int(utils.getSize(categoryFile) == 0):
258                             os.remove(categoryFile)
259             
260                 
261             # Copying media files from device user folder if media category is
262             # True in categories dictionary.
263             if ("media" in categories) and (categories["media"]):
264                 self.currentCategory = "media"
265                 result = self._backupMedias(workPath)
266                 if result == -1:
267                     os.chdir(current_dir)
268                     return 0
269                 # Update backup files number count
270                 self.backedUpNumber += result
271                 self._updateCategoryCount(filesByCategory, "media", result)
272                 zipPath = os.path.join(self.backupPath,'media.zip')
273                 if int(utils.getSize(zipPath) == 0):
274                     os.remove(zipPath)
275             
276             
277             # Copying documents files from device user folder if documents
278             # category is True in categories dictionary.
279             if ("documents" in categories) and (categories["documents"]):
280                 self.currentCategory = "documents"
281                 result = self._backupDocuments(workPath)
282                 # Update backup files number count
283                 self.backedUpNumber += result
284                 if result == -1:
285                     os.chdir(current_dir)
286                     return 0
287                 self._updateCategoryCount(filesByCategory, "documents", 
288                                           result)
289                 zipPath = os.path.join(self.backupPath, 'documents.zip')
290                 if int(utils.getSize(zipPath) == 0):
291                     os.remove(zipPath)          
292                 
293                 
294             # Change to previous work directory
295             os.chdir(current_dir)
296            
297             if self.backedUpNumber == 0:
298                 utils.removePath(self.backupPath)
299                 return -1
300             
301             # Create Backup Object, add it to this manager backup list and save
302             # the list in the backup file.
303             backup_size = utils.getSize(self.backupPath)
304             backupInfo = PcsBackupInfo(backup_name, path, backup_size, comment)
305             backupInfo.setFilesNumber(self.backedUpNumber)
306             self._backupList.append(backupInfo)
307             self.saveBackups()
308             
309             createXml(backupInfo, filesByCategory, host_ip)
310             self.setBackupInProgress(False)
311         finally:
312             utils.unmountDevice(mountPoint)
313             
314         return backupInfo
315     
316     
317     def _backupDocuments(self, workPath):
318         """ Create backup of documents files in user folder. """
319         categoryFilesNumber = 0
320         destinationPath = os.path.join(self.backupPath,'documents.zip')
321         if os.path.exists(destinationPath):
322             zipfile = utils.openZip(destinationPath, 'a')
323         else:
324             zipfile = utils.openZip(destinationPath, 'w')
325             
326         docsPath = os.path.join(self.myDocsPath, ".documents")
327         
328         if os.path.exists(docsPath):
329             backupPath = os.path.join(workPath, docsPath)
330             categoryFilesNumber = self.zipFolder(backupPath, zipfile, docsPath,
331                                                  "documents")
332             
333         utils.closeZip(zipfile)
334         return categoryFilesNumber
335            
336                     
337     def _backupMedias(self, workPath):
338         """ Create backup of media files in user folder. """
339         categoryFilesNumber = 0
340         destinationPath = os.path.join(self.backupPath,'media.zip')
341         if os.path.exists(destinationPath):
342             zipfile = utils.openZip(destinationPath, 'a')
343         else:
344             zipfile = utils.openZip(destinationPath, 'w')
345             
346         userFilesPath = self.myDocsPath
347         if os.path.exists(os.path.join(userFilesPath)):
348             for folder in os.listdir(userFilesPath):
349                 if folder != '.documents':
350                     objPath = os.path.join(userFilesPath, folder)
351                     backupDir = os.path.join(workPath, objPath)
352                     result = self.zipFolder(backupDir, zipfile,
353                                                          objPath, "media")
354                     if result != -1:
355                         categoryFilesNumber += result
356                     else:
357                         return result
358                     
359         utils.closeZip(zipfile)
360         return categoryFilesNumber
361     
362                     
363     def _createBackupFolder(self, newBackupPath):
364         if not utils.createFolder(newBackupPath):
365             return False
366     
367     
368     def _emitProgress(self):
369         currentSize = utils.getSize(self.backupPath) 
370         percentage = self.computePercentage(self.totalSize, currentSize)
371         self.emit(SIGNAL("backupProgress"), (percentage, self.currentCategory))
372     
373     
374     def _getTotalSize(self, backupFileDict, categories):
375         size = 0
376         for category in backupFileDict.keys():
377             for backupItem in backupFileDict[category]:
378                 if (category in categories) and (categories[category]):
379                     objPath = backupItem.path.replace('/','Root/',1)
380                     size += utils.getSize(objPath)
381         
382         if categories["documents"]:
383             size += utils.getSize(os.path.join(self.myDocsPath,
384                                                      ".documents"))
385         if categories["media"]:
386             for folder in os.listdir(self.myDocsPath):                
387                 if folder != '.documents':
388                     objPath = os.path.join(self.myDocsPath, folder)
389                     size += utils.getSize(objPath)
390         return size
391     
392     def _updateCategoryCount(self, filesByCategory, category, backed_up_number):
393         if str(category) not in filesByCategory.keys():
394             filesByCategory[category] = backed_up_number
395         else:
396             filesByCategory[category] += backed_up_number
397     
398             
399     def _verify_backup_name(self, backup_name):
400         """ Check if already exists any backup with the given name inside the
401         pc backup list. In case there is one, this function will return another
402         name to be set as the new backup name, else it will return the same 
403         name that was given.
404         
405         """
406         if self.getBackupInfo(backup_name) != None:
407             counter = 1
408             new_name = backup_name + "%02d" % counter
409             while(self.getBackupInfo(new_name)) != None:
410                 counter += 1
411                 new_name = backup_name + "%02d" % counter
412             
413             backup_name = new_name
414         return backup_name
415     
416     def zipFolder(self, path, zipfile, obj_path, category):
417         # Compress the folder from the given path, with all directories and
418         #   files inside it
419         # zipfile: The ZipFile object to append the folder
420         # obj_path: The ZipFile path
421         count = 0
422         self._emitProgress()
423         if os.path.exists(path):
424             if os.path.isdir(path):
425                 files = os.listdir(path)
426                 for node in files:
427                     if os.path.isdir(os.path.join(path, node)):
428                         zipCount = self.zipFolder(os.path.join(path, node), zipfile,
429                                             os.path.join(obj_path, node), category)
430                         if zipCount == -1:
431                             return -1
432                         else:
433                             count += zipCount
434                     else:
435                         # Check if backup was canceled and return -1 case positive
436                         if not self.backupInProgress:
437                             utils.removePath(self.backupPath)
438                             return -1
439
440                         if utils.zip(zipfile, os.path.join(obj_path, node)):
441                             count += 1
442                             self._emitProgress()
443             else:
444                 # Check if backup was canceled and return -1 case positive
445                 if not self.backupInProgress:
446                     utils.removePath(self.backupPath)
447                     return -1
448                 
449                 if utils.zip(zipfile, obj_path):
450                     count += 1
451                     self._emitProgress()
452         return count
453     
454
455
456 class NewBackupThread(QThread):
457     def __init__(self, manager, backupName, path, hostIp, categories, comment):
458         QThread.__init__(self)
459         
460         self.manager = manager
461         self.backupName = backupName
462         self.path = path
463         self.hostIp = hostIp
464         self.categories = categories
465         self.comment = comment
466     
467     def run(self):
468         ret = self.manager._runCreateBackup (self.backupName, self.path,
469                                              self.hostIp, self.categories,
470                                              str(self.comment))
471         self.manager.emit(SIGNAL("backupDone"), ret, 
472                           (self.manager.totalSize, self.manager.backedUpNumber))    
473
474
475