def testSaveLoad(self): prefs = Preferences('prefstest.conf') self.assertEqual(None, prefs.btDevice) self.assertEqual('bluetooth', prefs.connectionMethod) self.assertEqual('', prefs.customDevice) self.assertEqual(0, prefs.gammuIndex) prefs.btDevice = BluetoothDevice('00:00:00:00', 42, 'deviceName', 'serviceName') prefs.connectionMethod = 'connection' prefs.customDevice = '/dev/rfcomm0' prefs.gammuIndex = 2 prefs.save() prefsLoaded = Preferences('prefstest.conf') prefsLoaded.load() self.assertNotEqual(None, prefsLoaded.btDevice, "Device has not been loaded") self.assertEqual('00:00:00:00', prefsLoaded.btDevice.address) self.assertEqual(42, prefsLoaded.btDevice.port) self.assertEqual('deviceName', prefsLoaded.btDevice.deviceName) self.assertEqual('serviceName', prefsLoaded.btDevice.serviceName) self.assertEqual('connection', prefsLoaded.connectionMethod) self.assertEqual('/dev/rfcomm0', prefsLoaded.customDevice) self.assertEqual(2, prefsLoaded.gammuIndex)
class Main: def __init__(self): pygame.init() pygame.display.init() pygame.font.init() self.screen_w = pygame.display.Info().current_w self.screen_h = pygame.display.Info().current_h self.w = int(self.screen_h * 1.2) self.h = int(self.screen_h * 0.8) self.preferences = Preferences() self.fullscreen = self.preferences.get("fullscreen") self.go_mode() pygame.mouse.set_visible(False) pygame.display.set_caption(Constants.CAPTION) def go_mode(self): if self.fullscreen: self.mode = (self.screen_w, self.screen_h) if not self.mode in pygame.display.list_modes(): self.mode = pygame.display.list_modes()[0] self.screen = pygame.display.set_mode(self.mode, pygame.FULLSCREEN) else: self.mode = (self.w, self.h) self.screen = pygame.display.set_mode(self.mode) self.unit = int(self.mode[1] / Constants.UNITS) def main(self): self.boot_screen() while True: if not self.title_screen(): break if self.preferences.edit_flag: self.preferences.save() fullscreen = self.preferences.get("fullscreen") if self.fullscreen != fullscreen: self.fullscreen = fullscreen self.go_mode() else: self.play_game() def boot_screen(self): bs = BootScreen(self.screen, self.unit) bs.main() return bs.running def title_screen(self): self.go_mode() ts = TitleScreen(self.screen, self.unit, self.preferences) ts.main() return ts.running def play_game(self): gm = Game(self.screen, self.unit, self.preferences) gm.main() return gm.running
def show_preferences(self, widget): widget.set_sensitive(False) preferences = Preferences() response = preferences.run() if response == Gtk.ResponseType.ACCEPT: preferences.save() self.load_preferences() self.set_icon(self.is_monitoring) preferences.destroy() widget.set_sensitive(True)
def show_preferences(self, widget): widget.set_sensitive(False) preferences = Preferences() Keybinder.unbind(self.new_task_keybind) Keybinder.unbind(self.show_tasks_keybind) response = preferences.run() if response == Gtk.ResponseType.ACCEPT: preferences.save() self.load_preferences() self.load_todos() self.set_icon() widget.set_sensitive(True) preferences.destroy()
def show_preferences(self, widget): widget.set_sensitive(False) preferences_dialog = Preferences() response = preferences_dialog.run() if response == Gtk.ResponseType.ACCEPT: preferences_dialog.save() configuration = Configuration() preferences = configuration.get('preferences') self.theme_light = preferences['theme-light'] self.application_name = preferences['application_name'] if self.gotify_client is not None and \ self.gotify_client.is_running(): self.stop() self.start() preferences_dialog.destroy() widget.set_sensitive(True)
class MusicGuru(MusicGuruBase, ApplicationBase): LOGO_NAME = 'mg_logo' def __init__(self): appdata = str( QDesktopServices.storageLocation(QDesktopServices.DataLocation)) MusicGuruBase.__init__(self, appdata) ApplicationBase.__init__(self) if not op.exists(appdata): os.makedirs(appdata) logging.basicConfig(filename=op.join(appdata, 'debug.log'), level=logging.WARNING) self.prefs = Preferences() self.prefs.load() self.selectedBoardItems = [] self.selectedLocation = None self.mainWindow = MainWindow(app=self) self.locationsPanel = LocationsPanel(app=self) self.detailsPanel = DetailsPanel(app=self) self.ignoreBox = IgnoreBox(app=self) self.progress = Progress(self.mainWindow) self.aboutBox = AboutBox(self.mainWindow, self) self.connect(self.progress, SIGNAL('finished(QString)'), self.jobFinished) self.connect(self, SIGNAL('applicationFinishedLaunching()'), self.applicationFinishedLaunching) #--- Private def _placeDetailsPanel(self): # locations panel must be placed first if self.detailsPanel.isVisible(): return desktop = QApplication.desktop() w = self.locationsPanel.width() h = self.detailsPanel.height() x = self.locationsPanel.x() windowBottom = self.locationsPanel.frameGeometry().y( ) + self.locationsPanel.frameGeometry().height() y = windowBottom self.detailsPanel.move(x, y) self.detailsPanel.resize(w, h) def _placeIgnoreBox(self): if self.ignoreBox.isVisible(): return desktop = QApplication.desktop() windowWidth = self.mainWindow.frameGeometry().width() frameWidth = self.ignoreBox.frameGeometry().width( ) - self.ignoreBox.width() w = windowWidth - frameWidth h = self.ignoreBox.height() x = self.mainWindow.x() windowBottom = self.mainWindow.frameGeometry().y( ) + self.mainWindow.frameGeometry().height() y = min(windowBottom, desktop.height() - h) self.ignoreBox.move(x, y) self.ignoreBox.resize(w, h) def _placeLocationsPanel(self): if self.locationsPanel.isVisible(): return desktop = QApplication.desktop() w = self.locationsPanel.width() windowHeight = self.mainWindow.frameGeometry().height() frameHeight = self.locationsPanel.frameGeometry().height( ) - self.locationsPanel.height() h = windowHeight - frameHeight - self.detailsPanel.frameGeometry( ).height() windowRight = self.mainWindow.frameGeometry().x( ) + self.mainWindow.frameGeometry().width() x = min(windowRight, desktop.width() - w) y = self.mainWindow.y() self.locationsPanel.move(x, y) self.locationsPanel.resize(w, h) def _setup_as_registered(self): self.prefs.registration_code = self.registration_code self.prefs.registration_email = self.registration_email self.prefs.save() self.mainWindow.actionRegister.setVisible(False) self.aboutBox.registerButton.hide() self.aboutBox.registeredEmailLabel.setText( self.prefs.registration_email) def _startJob(self, jobid, func): title = JOBID2TITLE[jobid] try: j = self.progress.create_job() self.progress.run(jobid, title, func, args=(j, )) except job.JobInProgressError: msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." QMessageBox.information(self.mainWindow, "Action in progress", msg) #--- Public def addLocation(self, path, name, removeable): def do(j): MusicGuruBase.AddLocation(self, path, name, removeable, j) error_msg = self.CanAddLocation(path, name) if error_msg: QMessageBox.warning(self.mainWindow, "Add Location", error_msg) return self._startJob(JOB_ADD, do) def addLocationPrompt(self): dialog = AddLocationDialog(self) result = dialog.exec_() if result == QDialog.Accepted: self.addLocation(dialog.locationPath, dialog.locationName, dialog.isLocationRemovable) def askForRegCode(self): if self.reg.ask_for_code(): self._setup_as_registered() def copyOrMove(self, copy): def onNeedCd(location): # We can't do anything GUI related in a separate thread with Qt. Since copy/move # operations are performed asynchronously, the calls made to needCdDialog (created in # the main thread) must also be made asynchronously. return needCdDialog.askForDiskAsync(location.name) def do(j): MusicGuruBase.CopyOrMove(self, copy, dirpath, j, onNeedCd) needCdDialog = DiskNeededDialog() title = "Choose a destination" flags = QFileDialog.ShowDirsOnly dirpath = str( QFileDialog.getExistingDirectory(self.mainWindow, title, '', flags)) if dirpath: jobid = JOB_MATERIALIZE_COPY if copy else JOB_MATERIALIZE_MOVE self._startJob(jobid, do) def massRename(self, model, whitespace): def do(j): self.board.MassRename(model, whitespace, j) self._startJob(JOB_MASS_RENAME, do) def moveConflicts(self, with_original=False): if self.board.MoveConflicts(with_original=with_original) > 0: self.emit(SIGNAL('boardChanged()')) self.emit(SIGNAL('ignoreBoxChanged()')) def moveSelectedToIgnoreBox(self): smart_move(self.selectedBoardItems, self.board.ignore_box, allow_merge=True) self.emit(SIGNAL('boardChanged()')) self.emit(SIGNAL('ignoreBoxChanged()')) def removeEmptyFolders(self): MusicGuruBase.RemoveEmptyDirs(self) self.emit(SIGNAL('boardChanged()')) def removeLocation(self, location): self.board.RemoveLocation(location) location.delete() self.emit(SIGNAL('locationsChanged()')) self.emit(SIGNAL('boardChanged()')) def removeLocationPrompt(self): location = self.selectedLocation if location is None: return title = "Remove location" msg = "Do you really want to remove location {0}?".format( location.name) buttons = QMessageBox.Yes | QMessageBox.No answer = QMessageBox.question(self.mainWindow, title, msg, buttons, QMessageBox.Yes) if answer != QMessageBox.Yes: return self.removeLocation(location) def renameInRespectiveLocations(self): def do(j): MusicGuruBase.RenameInRespectiveLocations(self, j) self._startJob(JOB_MATERIALIZE_RENAME, do) def selectBoardItems(self, items): self.selectedBoardItems = items self.emit(SIGNAL('boardSelectionChanged()')) def selectLocation(self, location): self.selectedLocation = location def showAboutBox(self): self.aboutBox.show() def showDetailsPanel(self): self._placeLocationsPanel() self._placeDetailsPanel() self.detailsPanel.show() self.detailsPanel.activateWindow() def showHelp(self): url = QUrl.fromLocalFile(op.join(op.abspath(HELP_PATH), 'intro.htm')) QDesktopServices.openUrl(url) def showIgnoreBox(self): self._placeIgnoreBox() self.ignoreBox.show() self.ignoreBox.activateWindow() def showLocationPanel(self): self._placeLocationsPanel() self.locationsPanel.show() self.locationsPanel.activateWindow() def split(self, model, capacity, grouping_level): def do(j): self.board.Split(model, capacity, grouping_level, j) self._startJob(JOB_SPLIT, do) def toggleLocation(self, location): self.board.ToggleLocation(location) self.emit(SIGNAL('locationsChanged()')) self.emit(SIGNAL('boardChanged()')) def undoSplit(self): self.board.Unsplit() self.emit(SIGNAL('boardChanged()')) def updateCollection(self): def do(j): self.collection.update_volumes(j) self._startJob(JOB_UPDATE, do) def updateLocation(self, location): def do(j): location.update(None, j) self._startJob(JOB_UPDATE, do) #--- Events def applicationFinishedLaunching(self): self.reg = Registration(self) self.set_registration(self.prefs.registration_code, self.prefs.registration_email) if not self.registered and self.unpaid_hours >= 1: self.reg.show_nag() self.mainWindow.show() self.showLocationPanel() self.showDetailsPanel() self.updateCollection() def jobFinished(self, jobid): if jobid in (JOB_UPDATE, JOB_ADD): self.emit(SIGNAL('locationsChanged()')) if jobid in (JOB_MASS_RENAME, JOB_SPLIT): self.emit(SIGNAL('boardChanged()')) if jobid in (JOB_MATERIALIZE_RENAME, JOB_MATERIALIZE_MOVE): self.board.Empty() self.emit(SIGNAL('locationsChanged()')) self.emit(SIGNAL('boardChanged()')) self.emit(SIGNAL('ignoreBoxChanged()'))
class MainFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, size=(800,600)) # main menue definition fileMenu = wx.Menu() menuNew = fileMenu.Append(wx.ID_NEW, '&Import', 'Put directory under checksum control') self.Bind(wx.EVT_MENU, self.OnNew, menuNew) menuOpen = fileMenu.Append(wx.ID_OPEN, '&Open', 'Open directory under checksum control') self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen) fileMenu.AppendSeparator() menuPreferences = fileMenu.Append(wx.ID_PREFERENCES, '&Preferences\tCtrl+P', 'Show program\'s preferences') self.Bind(wx.EVT_MENU, self.OnPreferences, menuPreferences) fileMenu.AppendSeparator() menuExit = fileMenu.Append(wx.ID_EXIT, 'E&xit', 'Terminate Program') self.Bind(wx.EVT_MENU, self.OnExit, menuExit) actionMenu = wx.Menu() menuCheck = actionMenu.Append(wx.ID_FILE, '&Check\tCtrl+K', 'Check') self.Bind(wx.EVT_MENU, self.OnCheck, menuCheck) helpMenu = wx.Menu() menuAbout = helpMenu.Append(wx.ID_ABOUT, '&About', 'Information about this program') self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout) # assemble menu menuBar = wx.MenuBar() menuBar.Append(fileMenu, '&File') menuBar.Append(actionMenu, '&Action') menuBar.Append(helpMenu, '&Help') self.SetMenuBar(menuBar) # current directories and files self.UpdateRootDir(None) self.preferences = None # main window consists of address line and directory listing self.address = wx.TextCtrl(self, -1, style=wx.TE_READONLY) self.list = ListControlPanel(self) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.address, 0, wx.ALL | wx.EXPAND, 5) sizer.Add(self.list, 1, wx.ALL | wx.EXPAND, 5) self.SetSizer(sizer) self.statusbar = self.CreateStatusBar() self.statusbar.SetFieldsCount(1) self.Show(True) def SetAddressLine(self, path=''): self.address.SetValue(path) def SetStatusBarText(self, text=''): self.statusbar.SetStatusText(text) def UpdateRootDir(self, rootDir): if rootDir is None: self.rootDir = None self.metaDir = None self.dbFile = None self.sigFile = None self.preferencesFile = None self.Title = ProgramName + ' ' + ProgramVersion else: self.rootDir = rootDir self.metaName = '.' + ProgramName self.metaDir = os.path.join(self.rootDir, self.metaName) self.dbFile = os.path.join(self.metaDir, u'base.sqlite3') self.sigFile = os.path.join(self.metaDir, u'base.signature') self.preferencesFile = os.path.join(self.metaDir, u'preferences.json') self.Title = ProgramName + ' ' + ProgramVersion + \ ' - ' + self.rootDir def OnNew(self, event): # get a valid path from user dirDialog = wx.DirDialog(self, "Choose a directory for import:", \ style=wx.DD_DEFAULT_STYLE) if platform.system() == 'Windows': dirDialog.SetPath('D:\\Projects\\treeseal-example') # TESTING else: dirDialog.SetPath('/home/phil/Projects/treeseal-example') # TESTING if dirDialog.ShowModal() == wx.ID_OK: self.UpdateRootDir(dirDialog.GetPath()) else: self.UpdateRootDir(None) return # check pre-conditions if os.path.exists(self.metaDir): dial = wx.MessageBox('Path "' + self.rootDir + '" already seems to be under checksum control.\n\nDo you still want to continue?', \ 'Warning', wx.YES_NO | wx.ICON_WARNING | wx.NO_DEFAULT) if not dial == wx.YES: self.UpdateRootDir(None) return # close eventually existing previous instance self.list.ClearInstance() self.SetStatusBarText() self.Import() def OnOpen(self, event): # get a valid path from user dirDialog = wx.DirDialog(self, "Choose a directory for open:", \ style=wx.DD_DEFAULT_STYLE) if platform.system() == 'Windows': dirDialog.SetPath('D:\\Projects\\treeseal-example') # TESTING else: dirDialog.SetPath('/home/phil/Projects/treeseal-example') # TESTING if dirDialog.ShowModal() == wx.ID_OK: self.UpdateRootDir(dirDialog.GetPath()) else: self.UpdateRootDir(None) return # check pre-conditions if not os.path.exists(self.metaDir): wx.MessageBox('Path "' + self.rootDir + '" is no valid root dir.', \ 'Error', wx.OK | wx.ICON_ERROR) self.UpdateRootDir(None) return if not os.path.exists(self.dbFile): wx.MessageBox('Cannot find database file "' + self.dbFile + '".', \ 'Error', wx.OK | wx.ICON_ERROR) self.UpdateRootDir(None) return if not os.path.exists(self.sigFile): dial = wx.MessageBox('Cannot find database signature file "' + self.sigFile + '".\n\nUse database without verification?', \ 'Warning', wx.YES_NO | wx.ICON_WARNING | wx.NO_DEFAULT) if not dial == wx.YES: self.UpdateRootDir(None) return else: cs = Checksum() cs.calculateForFile(self.dbFile) if not cs.isValidUsingSavedFile(self.sigFile): dial = wx.MessageBox('Database or database signature file have been corrupted.\n\nUse database without verification?', \ 'Warning', wx.YES_NO | wx.ICON_WARNING | wx.NO_DEFAULT) if not dial == wx.YES: self.UpdateRootDir(None) return # close eventually existing previous instance self.list.ClearInstance() self.SetStatusBarText() # load preferences or create default ones self.preferences = Preferences() if os.path.exists(self.preferencesFile): self.preferences.load(self.preferencesFile) else: self.preferences.save(self.preferencesFile) def OnPreferences(self, event): if self.preferences is None: wx.MessageBox('Import or Open directory before you can access its preferences.', \ 'Error', wx.OK | wx.ICON_ERROR) return preferencesDialog = PreferencesDialog(self, self.preferences) preferencesDialog.ShowModal() self.preferences.save(self.preferencesFile) def OnExit(self, event): self.Close(True) def Import(self): # do not care about previous content: reset meta directory and database files if os.path.exists(self.metaDir): if os.path.exists(self.dbFile): os.remove(self.dbFile) if os.path.exists(self.sigFile): os.remove(self.sigFile) if os.path.exists(self.preferencesFile): os.remove(self.preferencesFile) else: os.mkdir(self.metaDir) # if on windows platform, hide directory if platform.system() == 'Windows': os.system('attrib +h "' + self.metaDir + '"') self.preferences = Preferences() preferencesDialog = PreferencesDialog(self, self.preferences) preferencesDialog.ShowModal() self.preferences.save(self.preferencesFile) try: # create trees fstree = FilesystemTree(self.rootDir, self.preferences.includes, \ [ os.path.sep + self.metaName ] + self.preferences.excludes) fstree.open() dbtree = DatabaseTree(self.dbFile, self.sigFile) dbtree.open() except MyException as e: e.showDialog('Importing ' + self.rootDir) return try: # create progress dialog progressDialog = FileProcessingProgressDialog(self, 'Importing ' + self.rootDir) progressDialog.Show() stats = fstree.getNodeStatistics() progressDialog.Init(stats.getNodeCount(), stats.getNodeSize()) # execute task fstree.registerHandlers(progressDialog.SignalNewFile, \ progressDialog.SignalBytesDone) fstree.copyTo(dbtree) dbtree.commit() fstree.unRegisterHandlers() except UserCancelledException: progressDialog.SignalFinished() return except MyException as e: progressDialog.Destroy() e.showDialog('Importing ' + self.rootDir) return # signal that we have returned from calculation, either # after it is done or after progressDialog signalled that the # user stopped the calculation using the cancel button progressDialog.SignalFinished() self.list.SetInstance(Instance(self.preferences, dbtree, None, None)) fstree.close() self.list.readonly = True self.SetStatusBarText('Imported ' + str(stats)) def OnCheck(self, event): # close eventually existing previous instance self.list.ClearInstance() self.SetStatusBarText() try: # create trees fstree = FilesystemTree(self.rootDir, self.preferences.includes, \ [ os.path.sep + self.metaName ] + self.preferences.excludes) fstree.open() dbtree = DatabaseTree(self.dbFile, self.sigFile) dbtree.open() memtree = MemoryTree() memtree.open() except MyException as e: e.showDialog('Checking ' + self.rootDir) return try: # create progress dialog progressDialog = FileProcessingProgressDialog(self, 'Checking ' + self.rootDir) progressDialog.Show() stats = fstree.getNodeStatistics() progressDialog.Init(stats.getNodeCount(), stats.getNodeSize()) # execute task fstree.registerHandlers(progressDialog.SignalNewFile, \ progressDialog.SignalBytesDone) fstree.diff(dbtree, memtree) memtree.commit() fstree.unRegisterHandlers() except UserCancelledException: progressDialog.SignalFinished() return except MyException as e: progressDialog.Destroy() e.showDialog('Checking ' + self.rootDir) return # signal that we have returned from calculation, either # after it is done or after progressDialog signalled that the # user stopped the calcuation using the cancel button progressDialog.SignalFinished() self.list.SetInstance(Instance(self.preferences, memtree, dbtree, fstree)) self.list.readonly = False self.SetStatusBarText('Checked ' + str(stats)) def OnAbout(self, event): info = wx.AboutDialogInfo() #info.SetIcon(wx.Icon('hunter.png', wx.BITMAP_TYPE_PNG)) info.SetName('treeseal') info.SetVersion('3.0') info.SetDescription('TreeSeal is a tool for checking the integrity ' + \ 'of a directory structure by keeping checksums and meta ' + \ 'information for each file in a separate database.') info.SetCopyright('(C) 2010 - 2013 Philipp Roebrock') info.SetWebSite('https://github.com/proebrock/treeseal') #info.SetLicence(licence) info.AddDeveloper('Philipp Roebrock') info.AddDocWriter('Philipp Roebrock') wx.AboutBox(info)
class TTS_CLI: def __init__(self): self.preferences = Preferences() parser = argparse.ArgumentParser( description="Manipulate Tabletop Simulator files") parser.add_argument("-d", "--directory", help="Override TTS cache directory") parser.add_argument("-l", "--loglevel", help="Set logging level", choices=['debug', 'info', 'warn', 'error']) subparsers = parser.add_subparsers(dest='parser', title='command', description='Valid commands.') subparsers.required = True # add list command parser_list = subparsers.add_parser('list', help="List installed mods.", description=''' List installed mods. If no id is provided, then this will return a list of all installed modules. If an id is provided, then this will list the contents of that modules. ''') group_list = parser_list.add_mutually_exclusive_group() group_list.add_argument("-w", "--workshop", action="store_const", metavar='save_type', dest='save_type', const=SaveType.workshop, help="List workshop files (the default).") group_list.add_argument("-s", "--save", action="store_const", metavar='save_type', dest='save_type', const=SaveType.save, help="List saves.") group_list.add_argument("-c", "--chest", action="store_const", metavar='save_type', dest='save_type', const=SaveType.chest, help="List chest files.") parser_list.add_argument("id", nargs='?', help="ID of specific mod to list details of.") parser_list.set_defaults(func=self.do_list) # export command parser_export = subparsers.add_parser( 'export', help="Export a mod.", description='Export a mod in a format suitible for later import.') group_export = parser_export.add_mutually_exclusive_group() group_export.add_argument("-w", "--workshop", action="store_const", dest='save_type', metavar='save_type', const=SaveType.workshop, help="ID is of workshop file (the default).") group_export.add_argument("-s", "--save", action="store_const", dest='save_type', metavar='save_type', const=SaveType.save, help="ID is of savegame file.") group_export.add_argument("-c", "--chest", action="store_const", dest='save_type', metavar='save_type', const=SaveType.chest, help="ID is of chest file.") parser_export.add_argument( "id", help="ID of mod/name of savegame to export.") parser_export.add_argument("-o", "--output", help="Location/file to export to.") parser_export.add_argument("-f", "--force", action="store_true", help="Force creation of export file.") parser_export.add_argument( "-d", "--download", action="store_true", help="Attempt to download missing cache files. (EXPERIMENTAL)") parser_export.set_defaults(func=self.do_export) # import command parser_import = subparsers.add_parser( 'import', help="Import a mod.", description="Import an previously exported mod.") parser_import.add_argument("file", help="Mod pak file to import.") parser_import.set_defaults(func=self.do_import) # download command parser_download = subparsers.add_parser( 'download', help='Download mod files.', description= 'Attempt to download any missing files for an installed mod.') group_download = parser_download.add_mutually_exclusive_group() group_download.add_argument("-w", "--workshop", action="store_const", dest='save_type', metavar='save_type', const=SaveType.workshop, help="ID is of workshop file.") group_download.add_argument("-s", "--save", action="store_const", dest='save_type', metavar='save_type', const=SaveType.save, help="ID is of savegame file.") group_download.add_argument("-c", "--chest", action="store_const", dest='save_type', metavar='save_type', const=SaveType.chest, help="ID is of chest file.") group_download_target = parser_download.add_mutually_exclusive_group( required=True) group_download_target.add_argument("-a", "--all", action="store_true", help="Download all.") group_download_target.add_argument( "id", nargs='?', help="ID of mod/name of savegame to download.") parser_download.set_defaults(func=self.do_download) # cache command parser_cache = subparsers.add_parser('cache', help='Work with the cache.') subparsers_cache = parser_cache.add_subparsers( dest='parser_cache', title='cache_command', description='Valid sub-commands.') subparsers_cache.required = True parser_cache_create = subparsers_cache.add_parser( 'create', help='(re)create cache directory') parser_cache_create.set_defaults(func=self.do_cache_create) # config command parser_config = subparsers.add_parser('config', help='Configure tts manager.') subparsers_config = parser_config.add_subparsers( dest='parser_config', title='config_command', description='Valid sub-commands.') subparsers_config.required = True parser_config_list = subparsers_config.add_parser( 'list', help='List configuration.') parser_config_list.set_defaults(func=self.do_config_list) parser_config_validate = subparsers_config.add_parser( 'validate', help='Validate configuration.') parser_config_validate.set_defaults(func=self.do_config_validate) parser_config_reset = subparsers_config.add_parser( 'reset', help='Reset configuration.') parser_config_reset.set_defaults(func=self.do_config_reset) parser_config_set = subparsers_config.add_parser( 'set', help='Set configuration parameters.') parser_config_set.set_defaults(func=self.do_config_set) parser_config_set.add_argument("-m", "--mod_location", choices=['documents', 'gamedata'], help="Where mods are stored.") parser_config_set.add_argument("-t", "--tts_location", help="TTS Install directory") args = parser.parse_args() # set logging if args.loglevel: logmap = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warn': logging.WARN, 'error': logging.ERROR } logger().setLevel(logmap[args.loglevel]) else: logger().setLevel(logging.WARN) # load filesystem values if args.directory: self.filesystem = FileSystem(os.path.abspath(args.directory)) else: self.filesystem = self.preferences.get_filesystem() if (args.parser == 'list' or args.parser == 'export') and not args.save_type: # set default args.save_type = SaveType.workshop if (args.parser == 'config' and args.parser_config == 'set' and not args.mod_location and not args.tts_location): parser_config_set.error("At least one of -m or -t is required.") rc, message = args.func(args) if message: print(message) sys.exit(rc) def do_config_set(self, args): if args.mod_location: self.preferences.locationIsUser = args.mod_location == 'documents' if args.tts_location: self.preferences.TTSLocation = args.mod_location self.preferences.save() return 0, "Preferences set" def do_config_reset(self, args): self.preferences.reset() return 0, "Preferences Reset." def do_config_list(self, args): return 0, self.preferences def do_config_validate(self, args): if self.preferences.validate(): return 0, "Configuration validated OK." else: return 1, "Configuration failed to validate." def do_cache_create(self, args): try: self.filesystem.create_dirs() except OSError as exception: return 1, "OS error: {0}".format(exception) return 0, "All directories created OK." def list_by_type(self, save_type): result = "" for (name, id) in describe_files_by_type(self.filesystem, save_type): result += "\n%s (%s)" % (name, id) return 0, result def list_item(self, data, filename, ident): if not data: # self.list_installed() return save = Save(savedata=data, ident=ident, filename=filename, filesystem=self.filesystem) return 0, save def do_download(self, args): successful = True if not args.all: if not args.save_type: args.save_type = self.filesystem.get_json_filename_type( args.id) if not args.save_type: return 1, "Unable to determine type of id %s" % args.id successful = download_file(self.filesystem, args.id, args.save_type) else: if args.save_type: for ident in self.filesystem.get_filenames_by_type( args.save_type): if not download_file(self.filesystem, ident, args.save_type): successful = False break else: for save_type in SaveType: for ident in self.filesystem.get_filenames_by_type( save_type): if not download_file(self.filesystem, ident, save_type): successful = False break if successful: return 0, "All files downloaded." else: return 1, "Some files failed to download." def do_list(self, args): rc = 0 result = None if not args.id: rc, result = self.list_by_type(args.save_type) else: if not args.save_type: args.save_type = self.filesystem.get_json_filename_type( args.id) if not args.save_type: return 1, "Unable to determine type of id %s" % args.id filename = self.filesystem.get_json_filename_for_type( args.id, args.save_type) data = load_json_file(filename) rc, result = self.list_item(data, filename, args.id) return rc, result def do_export(self, args): filename = None if args.output: if os.path.isdir(args.output): filename = os.path.join(args.output, args.id + ".pak") else: filename = args.output else: filename = args.id + ".pak" data = None json_filename = None if not args.save_type: args.save_type = self.filesystem.get_json_filename_type(args.id) if not args.save_type: return 1, "Unable to determine type of id %s" % args.id json_filename = self.filesystem.get_json_filename_for_type( args.id, args.save_type) if not json_filename: return 1, "Unable to find filename for id %s (wrong -s/-w/-c specified?)" % args.id data = load_json_file(json_filename) if not data: return 1, "Unable to load data for file %s" % json_filename save = Save(savedata=data, filename=json_filename, ident=args.id, save_type=args.save_type, filesystem=self.filesystem) if not save.isInstalled: if not args.download: return 1, "Unable to find all urls required by %s. Rerun with -d to try and download them or open it within TTS.\n%s" % ( args.id, save) else: logger().info("Downloading missing files...") successful = save.download() if successful: logger().info("Files downloaded successfully.") else: return 1, "Some files failed to download" if os.path.isfile(filename) and not args.force: return 1, "%s already exists. Please specify another file or use '-f'" % filename logger().info("Exporting json file %s to %s" % (args.id, filename)) save.export(filename) # TODO: exception handling return 0, "Exported %s to %s" % (args.id, filename) def do_import(self, args): return 0, save_helper.importPak(self.filesystem, args.file)
class MusicGuru(MusicGuruBase, ApplicationBase): LOGO_NAME = 'mg_logo' def __init__(self): appdata = str(QDesktopServices.storageLocation(QDesktopServices.DataLocation)) MusicGuruBase.__init__(self, appdata) ApplicationBase.__init__(self) if not op.exists(appdata): os.makedirs(appdata) logging.basicConfig(filename=op.join(appdata, 'debug.log'), level=logging.WARNING) self.prefs = Preferences() self.prefs.load() self.selectedBoardItems = [] self.selectedLocation = None self.mainWindow = MainWindow(app=self) self.locationsPanel = LocationsPanel(app=self) self.detailsPanel = DetailsPanel(app=self) self.ignoreBox = IgnoreBox(app=self) self.progress = Progress(self.mainWindow) self.aboutBox = AboutBox(self.mainWindow, self) self.connect(self.progress, SIGNAL('finished(QString)'), self.jobFinished) self.connect(self, SIGNAL('applicationFinishedLaunching()'), self.applicationFinishedLaunching) #--- Private def _placeDetailsPanel(self): # locations panel must be placed first if self.detailsPanel.isVisible(): return desktop = QApplication.desktop() w = self.locationsPanel.width() h = self.detailsPanel.height() x = self.locationsPanel.x() windowBottom = self.locationsPanel.frameGeometry().y() + self.locationsPanel.frameGeometry().height() y = windowBottom self.detailsPanel.move(x, y) self.detailsPanel.resize(w, h) def _placeIgnoreBox(self): if self.ignoreBox.isVisible(): return desktop = QApplication.desktop() windowWidth = self.mainWindow.frameGeometry().width() frameWidth = self.ignoreBox.frameGeometry().width() - self.ignoreBox.width() w = windowWidth - frameWidth h = self.ignoreBox.height() x = self.mainWindow.x() windowBottom = self.mainWindow.frameGeometry().y() + self.mainWindow.frameGeometry().height() y = min(windowBottom, desktop.height() - h) self.ignoreBox.move(x, y) self.ignoreBox.resize(w, h) def _placeLocationsPanel(self): if self.locationsPanel.isVisible(): return desktop = QApplication.desktop() w = self.locationsPanel.width() windowHeight = self.mainWindow.frameGeometry().height() frameHeight = self.locationsPanel.frameGeometry().height() - self.locationsPanel.height() h = windowHeight - frameHeight - self.detailsPanel.frameGeometry().height() windowRight = self.mainWindow.frameGeometry().x() + self.mainWindow.frameGeometry().width() x = min(windowRight, desktop.width() - w) y = self.mainWindow.y() self.locationsPanel.move(x, y) self.locationsPanel.resize(w, h) def _setup_as_registered(self): self.prefs.registration_code = self.registration_code self.prefs.registration_email = self.registration_email self.prefs.save() self.mainWindow.actionRegister.setVisible(False) self.aboutBox.registerButton.hide() self.aboutBox.registeredEmailLabel.setText(self.prefs.registration_email) def _startJob(self, jobid, func): title = JOBID2TITLE[jobid] try: j = self.progress.create_job() self.progress.run(jobid, title, func, args=(j, )) except job.JobInProgressError: msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." QMessageBox.information(self.mainWindow, "Action in progress", msg) #--- Public def addLocation(self, path, name, removeable): def do(j): MusicGuruBase.AddLocation(self, path, name, removeable, j) error_msg = self.CanAddLocation(path, name) if error_msg: QMessageBox.warning(self.mainWindow, "Add Location", error_msg) return self._startJob(JOB_ADD, do) def addLocationPrompt(self): dialog = AddLocationDialog(self) result = dialog.exec_() if result == QDialog.Accepted: self.addLocation(dialog.locationPath, dialog.locationName, dialog.isLocationRemovable) def askForRegCode(self): if self.reg.ask_for_code(): self._setup_as_registered() def copyOrMove(self, copy): def onNeedCd(location): # We can't do anything GUI related in a separate thread with Qt. Since copy/move # operations are performed asynchronously, the calls made to needCdDialog (created in # the main thread) must also be made asynchronously. return needCdDialog.askForDiskAsync(location.name) def do(j): MusicGuruBase.CopyOrMove(self, copy, dirpath, j, onNeedCd) needCdDialog = DiskNeededDialog() title = "Choose a destination" flags = QFileDialog.ShowDirsOnly dirpath = str(QFileDialog.getExistingDirectory(self.mainWindow, title, '', flags)) if dirpath: jobid = JOB_MATERIALIZE_COPY if copy else JOB_MATERIALIZE_MOVE self._startJob(jobid, do) def massRename(self, model, whitespace): def do(j): self.board.MassRename(model, whitespace, j) self._startJob(JOB_MASS_RENAME, do) def moveConflicts(self, with_original=False): if self.board.MoveConflicts(with_original=with_original) > 0: self.emit(SIGNAL('boardChanged()')) self.emit(SIGNAL('ignoreBoxChanged()')) def moveSelectedToIgnoreBox(self): smart_move(self.selectedBoardItems, self.board.ignore_box, allow_merge=True) self.emit(SIGNAL('boardChanged()')) self.emit(SIGNAL('ignoreBoxChanged()')) def removeEmptyFolders(self): MusicGuruBase.RemoveEmptyDirs(self) self.emit(SIGNAL('boardChanged()')) def removeLocation(self, location): self.board.RemoveLocation(location) location.delete() self.emit(SIGNAL('locationsChanged()')) self.emit(SIGNAL('boardChanged()')) def removeLocationPrompt(self): location = self.selectedLocation if location is None: return title = "Remove location" msg = "Do you really want to remove location {0}?".format(location.name) buttons = QMessageBox.Yes | QMessageBox.No answer = QMessageBox.question(self.mainWindow, title, msg, buttons, QMessageBox.Yes) if answer != QMessageBox.Yes: return self.removeLocation(location) def renameInRespectiveLocations(self): def do(j): MusicGuruBase.RenameInRespectiveLocations(self, j) self._startJob(JOB_MATERIALIZE_RENAME, do) def selectBoardItems(self, items): self.selectedBoardItems = items self.emit(SIGNAL('boardSelectionChanged()')) def selectLocation(self, location): self.selectedLocation = location def showAboutBox(self): self.aboutBox.show() def showDetailsPanel(self): self._placeLocationsPanel() self._placeDetailsPanel() self.detailsPanel.show() self.detailsPanel.activateWindow() def showHelp(self): url = QUrl.fromLocalFile(op.join(op.abspath(HELP_PATH), 'intro.htm')) QDesktopServices.openUrl(url) def showIgnoreBox(self): self._placeIgnoreBox() self.ignoreBox.show() self.ignoreBox.activateWindow() def showLocationPanel(self): self._placeLocationsPanel() self.locationsPanel.show() self.locationsPanel.activateWindow() def split(self, model, capacity, grouping_level): def do(j): self.board.Split(model, capacity, grouping_level, j) self._startJob(JOB_SPLIT, do) def toggleLocation(self, location): self.board.ToggleLocation(location) self.emit(SIGNAL('locationsChanged()')) self.emit(SIGNAL('boardChanged()')) def undoSplit(self): self.board.Unsplit() self.emit(SIGNAL('boardChanged()')) def updateCollection(self): def do(j): self.collection.update_volumes(j) self._startJob(JOB_UPDATE, do) def updateLocation(self, location): def do(j): location.update(None, j) self._startJob(JOB_UPDATE, do) #--- Events def applicationFinishedLaunching(self): self.reg = Registration(self) self.set_registration(self.prefs.registration_code, self.prefs.registration_email) if not self.registered and self.unpaid_hours >= 1: self.reg.show_nag() self.mainWindow.show() self.showLocationPanel() self.showDetailsPanel() self.updateCollection() def jobFinished(self, jobid): if jobid in (JOB_UPDATE, JOB_ADD): self.emit(SIGNAL('locationsChanged()')) if jobid in (JOB_MASS_RENAME, JOB_SPLIT): self.emit(SIGNAL('boardChanged()')) if jobid in (JOB_MATERIALIZE_RENAME, JOB_MATERIALIZE_MOVE): self.board.Empty() self.emit(SIGNAL('locationsChanged()')) self.emit(SIGNAL('boardChanged()')) self.emit(SIGNAL('ignoreBoxChanged()'))