def onTransactionAmountChanged(self, message): transaction = message.data if transaction.Parent == self: debug.debug("Updating balance because I am %s: %s" % (self.Name, transaction)) self.Balance = sum((t.Amount for t in self.Transactions)) else: debug.debug("Ignoring transaction because I am %s: %s" % (self.Name, transaction))
def loadProfiles(self): self.profiles = shippedProfiles try: contents = open(self.configFile, 'r') storedProfiles = json.load(contents) except Exception, e: debug.debug("Unable to read CSV profiles file:", e)
def syncBalances(self): debug.debug("Syncing balances...") # Load the model, necessary to sync the balances. model = self.GetModel() for account in model.Accounts: account.Balance = sum([t.Amount for t in account.Transactions]) self.commitIfAppropriate()
def onORMObjectUpdated(self, message): topic, data = message.topic, message.data classname, attrname = topic[-2:] ormobj = data table = ormobj.ORM_TABLE value = ormobj.getAttrValue(attrname) if isinstance(ormobj, ORMKeyValueObject): result = self.dbconn.cursor().execute( "UPDATE %s SET value=? WHERE name=?" % table, (repr(value), attrname)) else: # Figure out the name of the column colname = attrname.strip("_") colname = colname[0].lower() + colname[1:] colname = { "repeatOn": "repeatsOn", "source": "sourceId", "linkedTransaction": "linkId" }.get(colname, colname) query = "UPDATE %s SET %s=? WHERE id=?" % (table, colname) objId = ormobj.ID self.dbconn.cursor().execute(query, (value, objId)) self.commitIfAppropriate() debug.debug("Persisting %s.%s update (%s)" % (classname, attrname, value))
def onORMObjectUpdated(self, message): topic, data = message.topic, message.data classname, attrname = topic[-2:] ormobj = data table = ormobj.ORM_TABLE value = ormobj.getAttrValue(attrname) if isinstance(ormobj, ORMKeyValueObject): result = self.dbconn.cursor().execute( "UPDATE %s SET value=? WHERE name=?" % table, (repr(value), attrname) ) # @UnusedVariable else: # Figure out the name of the column colname = attrname.strip("_") colname = colname[0].lower() + colname[1:] colname = {"repeatOn": "repeatsOn", "source": "sourceId", "linkedTransaction": "linkId"}.get( colname, colname ) query = "UPDATE %s SET %s=? WHERE id=?" % (table, colname) objId = ormobj.ID self.dbconn.cursor().execute(query, (value, objId)) self.commitIfAppropriate() debug.debug("Persisting %s.%s update (%s)" % (classname, attrname, value))
def Save(self): import time t = time.time() self.dbconn.commit() debug.debug("Committed in %s seconds" % (time.time() - t)) self.Dirty = False
def MigrateIfFound(self, fromPath, toPath): """Migrate a file from fromPath (if it exists) to toPath.""" if os.path.exists(fromPath): import shutil try: shutil.move(fromPath, toPath) except IOError: debug.debug("Unable to move %s to %s, attempting a copy instead..." % (fromPath, toPath)) shutil.copyfile(fromPath, toPath) return True
def SetAutoSave(self, val): self._AutoSave = val wx.Config.Get().WriteBool("AUTO-SAVE", val) Publisher.sendMessage("controller.autosave_toggled", val) for model in self.Models: debug.debug("Setting auto-save to: %s" % val) model.Store.AutoSave = val # If the user enables auto-save, we want to also save. if self.AutoSave: Publisher.sendMessage("user.saved")
def MigrateIfFound(self, fromPath, toPath): """Migrate a file from fromPath (if it exists) to toPath.""" if os.path.exists(fromPath): import shutil try: shutil.move(fromPath, toPath) except IOError: debug.debug( "Unable to move %s to %s, attempting a copy instead..." % (fromPath, toPath)) shutil.copyfile(fromPath, toPath) return True
def __init__(self, path, autoSave=True): self.Subscriptions = [] self.Version = 13 self.Path = path self.AutoSave = False self.Dirty = False self.BatchDepth = 0 self.cachedModel = None # Upgrades can't enable syncing if needed from older versions. self.needsSync = False existed = True # See if the path already exists to decide what to do. if not os.path.exists(self.Path): existed = False # Initialize the connection and optimize it. connection = sqlite.connect(self.Path) self.dbconn = connection # Disable synchronous I/O, which makes everything MUCH faster, at the potential cost of durability. self.dbconn.execute("PRAGMA synchronous=OFF;") # If the db doesn't exist, initialize it. if not existed: debug.debug('Initializing', self.Path) self.initialize() else: debug.debug('Loading', self.Path) self.Meta = self.getMeta() debug.debug(self.Meta) while self.Meta['VERSION'] < self.Version: # If we are creating a new db, we don't need to backup each iteration. self.upgradeDb(self.Meta['VERSION'], backup=existed) self.Meta = self.getMeta() debug.debug(self.Meta) # We have to subscribe before syncing otherwise it won't get synced if there aren't other changes. self.Subscriptions = ( (self.onORMObjectUpdated, "ormobject.updated"), (self.onAccountBalanceChanged, "account.balance changed"), (self.onAccountRemoved, "account.removed"), (self.onBatchEvent, "batch"), (self.onExit, "exiting"), ) for callback, topic in self.Subscriptions: Publisher.subscribe(callback, topic) # If the upgrade process requires a sync, do so now. if self.needsSync: self.syncBalances() self.needsSync = False self.AutoSave = autoSave self.commitIfAppropriate()
def GetModel(self, useCached=True): if self.cachedModel is None or not useCached: debug.debug('Creating model...') self.cachedModel = BankModel(self) return self.cachedModel
def upgradeDb(self, fromVer, backup=True): if backup: # Make a backup source = self.Path dest = self.Path + ".backup-v%i-%s" % (fromVer, datetime.date.today().strftime("%Y-%m-%d")) debug.debug("Making backup to %s" % dest) import shutil try: shutil.copyfile(source, dest) except IOError: import traceback; traceback.print_exc() raise Exception("Unable to make backup before proceeding with database upgrade...bailing.") debug.debug('Upgrading db from %i' % fromVer) cursor = self.dbconn.cursor() if fromVer == 1: # Add `currency` column to the accounts table with default value 0. cursor.execute('ALTER TABLE accounts ADD currency INTEGER not null DEFAULT 0') # Add metadata table, with version: 2 cursor.execute('CREATE TABLE meta (id INTEGER PRIMARY KEY, name VARCHAR(255), value VARCHAR(255))') cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('VERSION', '2')) elif fromVer == 2: # Add `total` column to the accounts table. cursor.execute('ALTER TABLE accounts ADD balance FLOAT not null DEFAULT 0.0') # The total column will need to be synced. self.needsSync = True # Update the meta version number. elif fromVer == 3: # Add `linkId` column to transactions for transfers. cursor.execute('ALTER TABLE transactions ADD linkId INTEGER') elif fromVer == 4: # Add recurring transactions table. transactionBase = "id INTEGER PRIMARY KEY, accountId INTEGER, amount FLOAT, description VARCHAR(255), date CHAR(10)" recurringExtra = "repeatType INTEGER, repeatEvery INTEGER, repeatsOn VARCHAR(255), endDate CHAR(10)" cursor.execute('CREATE TABLE recurring_transactions (%s, %s)' % (transactionBase, recurringExtra)) elif fromVer == 5: cursor.execute('ALTER TABLE recurring_transactions ADD sourceId INTEGER') cursor.execute('ALTER TABLE recurring_transactions ADD lastTransacted CHAR(10)') elif fromVer == 6: # Handle LP: #496341 by ignoring the error if this table already exists. try: cursor.execute('ALTER TABLE transactions ADD recurringParent INTEGER') except sqlite3.OperationalError: debug.debug("Detected a broken database from the 0.4 -> 0.6 upgrade, handling appropriately.") elif fromVer == 7: # Force a re-sync for the 0.6.1 release after fixing LP: #496341 self.needsSync = True elif fromVer == 8: # Add the LastAccountId meta key. cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('LastAccountId', None)) elif fromVer == 9: # Mint integration cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('MintEnabled', False)) cursor.execute('ALTER TABLE accounts ADD mintId INTEGER') elif fromVer == 10: # The obvious index to create, had I known about indexes previously. # It is nice to use IF NOT EXISTS here as some devs and branch users might have it outside of an upgrade. cursor.execute('CREATE INDEX IF NOT EXISTS transactions_accountId_idx ON transactions(accountId)') # Due to bug LP #605591 there can be orphaned transactions which can cause errors if linked. # This is tested by testOrphanedTransactionsAreDeleted (dbupgradetests.DBUpgradeTest) # Also takes care of LP #249954 without the explicit need to defensively remove on any account creation. self.cleanOrphanedTransactions() elif fromVer == 11: self.needsSync = True elif fromVer == 12: # globalCurrency entry cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('GlobalCurrency', 0)) else: raise Exception("Cannot upgrade database from version %i"%fromVer) """ # Tagging infrastructure (currently unused due to speed of parsing on startup). cursor.execute('CREATE TABLE tags (id INTEGER PRIMARY KEY, name VARCHAR(255))') cursor.execute('CREATE TABLE transactions_tags_link (id INTEGER PRIMARY KEY, transactionId INTEGER, tagId INTEGER)') cursor.execute('CREATE INDEX transactions_tags_transactionId_idx ON transactions_tags_link(transactionId)') cursor.execute('CREATE INDEX transactions_tags_tagId_idx ON transactions_tags_link(tagId)') """ metaVer = fromVer + 1 cursor.execute('UPDATE meta SET value=? WHERE name=?', (metaVer, "VERSION")) self.commitIfAppropriate()
def upgradeDb(self, fromVer, backup=True): if backup: # Make a backup source = self.Path dest = self.Path + ".backup-v%i-%s" % ( fromVer, datetime.date.today().strftime("%Y-%m-%d")) debug.debug("Making backup to %s" % dest) import shutil try: shutil.copyfile(source, dest) except IOError: import traceback traceback.print_exc() raise Exception( "Unable to make backup before proceeding with database upgrade...bailing." ) debug.debug('Upgrading db from %i' % fromVer) cursor = self.dbconn.cursor() if fromVer == 1: # Add `currency` column to the accounts table with default value 0. cursor.execute( 'ALTER TABLE accounts ADD currency INTEGER not null DEFAULT 0') # Add metadata table, with version: 2 cursor.execute( 'CREATE TABLE meta (id INTEGER PRIMARY KEY, name VARCHAR(255), value VARCHAR(255))' ) cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('VERSION', '2')) elif fromVer == 2: # Add `total` column to the accounts table. cursor.execute( 'ALTER TABLE accounts ADD balance FLOAT not null DEFAULT 0.0') # The total column will need to be synced. self.needsSync = True # Update the meta version number. elif fromVer == 3: # Add `linkId` column to transactions for transfers. cursor.execute('ALTER TABLE transactions ADD linkId INTEGER') elif fromVer == 4: # Add recurring transactions table. transactionBase = "id INTEGER PRIMARY KEY, accountId INTEGER, amount FLOAT, description VARCHAR(255), date CHAR(10)" recurringExtra = "repeatType INTEGER, repeatEvery INTEGER, repeatsOn VARCHAR(255), endDate CHAR(10)" cursor.execute('CREATE TABLE recurring_transactions (%s, %s)' % (transactionBase, recurringExtra)) elif fromVer == 5: cursor.execute( 'ALTER TABLE recurring_transactions ADD sourceId INTEGER') cursor.execute( 'ALTER TABLE recurring_transactions ADD lastTransacted CHAR(10)' ) elif fromVer == 6: # Handle LP: #496341 by ignoring the error if this table already exists. try: cursor.execute( 'ALTER TABLE transactions ADD recurringParent INTEGER') except sqlite3.OperationalError: debug.debug( "Detected a broken database from the 0.4 -> 0.6 upgrade, handling appropriately." ) elif fromVer == 7: # Force a re-sync for the 0.6.1 release after fixing LP: #496341 self.needsSync = True elif fromVer == 8: # Add the LastAccountId meta key. cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('LastAccountId', None)) elif fromVer == 9: # Mint integration cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('MintEnabled', False)) cursor.execute('ALTER TABLE accounts ADD mintId INTEGER') elif fromVer == 10: # The obvious index to create, had I known about indexes previously. # It is nice to use IF NOT EXISTS here as some devs and branch users might have it outside of an upgrade. cursor.execute( 'CREATE INDEX IF NOT EXISTS transactions_accountId_idx ON transactions(accountId)' ) # Due to bug LP #605591 there can be orphaned transactions which can cause errors if linked. # This is tested by testOrphanedTransactionsAreDeleted (dbupgradetests.DBUpgradeTest) # Also takes care of LP #249954 without the explicit need to defensively remove on any account creation. self.cleanOrphanedTransactions() elif fromVer == 11: self.needsSync = True elif fromVer == 12: # globalCurrency entry cursor.execute('INSERT INTO meta VALUES (null, ?, ?)', ('GlobalCurrency', 0)) else: raise Exception("Cannot upgrade database from version %i" % fromVer) """ # Tagging infrastructure (currently unused due to speed of parsing on startup). cursor.execute('CREATE TABLE tags (id INTEGER PRIMARY KEY, name VARCHAR(255))') cursor.execute('CREATE TABLE transactions_tags_link (id INTEGER PRIMARY KEY, transactionId INTEGER, tagId INTEGER)') cursor.execute('CREATE INDEX transactions_tags_transactionId_idx ON transactions_tags_link(transactionId)') cursor.execute('CREATE INDEX transactions_tags_tagId_idx ON transactions_tags_link(tagId)') """ metaVer = fromVer + 1 cursor.execute('UPDATE meta SET value=? WHERE name=?', (metaVer, "VERSION")) self.commitIfAppropriate()