class FeideeUtils::Database

Constants

AllHeaders
AllKnownTables
FeideeHeader_Android
FeideeHeader_iOS
NoDeleteSuffixTables
PotentialUsefulTables
Tables

Attributes

last_modified_at[R]
ledger[R]
ledger_name[R]
missing_tables[R]
platform[R]
sqlite_file[R]

Public Class Methods

new(private_sqlite, strip = false) click to toggle source
Calls superclass method
# File lib/feidee_utils/database.rb, line 35
def initialize(private_sqlite, strip = false)
  @sqlite_file = Database.feidee_to_sqlite(private_sqlite)

  super(@sqlite_file.path)

  extract_metadata
  drop_unused_tables if strip

  # TODO: make Ledger a first class object.
  @ledger = Record.generate_subclasses(self)
end

Private Class Methods

feidee_to_sqlite(private_sqlite, sqlite_file = nil) click to toggle source
# File lib/feidee_utils/database.rb, line 121
def feidee_to_sqlite(private_sqlite, sqlite_file = nil)
  # Discard the first a few bytes content.
  private_header = private_sqlite.read(Header.length)

  unless AllHeaders.include? private_header
    raise "Unexpected private sqlite header #{private_header.inspect}."
  end

  # Write the rest to a tempfile.
  sqlite_file ||= Tempfile.new("kingdee_sqlite", binmode: true)
  sqlite_file.binmode
  sqlite_file.write(Header)
  sqlite_file.write(private_sqlite.read)
  sqlite_file.fsync
  sqlite_file
end
open_file(file_name) click to toggle source
# File lib/feidee_utils/database.rb, line 112
def open_file(file_name)
  Database.new(File.open(file_name))
end
sqlite_to_feidee(sqlite, feidee_file = nil, platform = :iOS) click to toggle source
# File lib/feidee_utils/database.rb, line 138
def sqlite_to_feidee(sqlite, feidee_file = nil, platform = :iOS)
  sqlite.read(Header.length)
  private_header = if platform == :iOS
                     FeideeHeader_iOS
                   else
                     FeideeHeader_Android
                   end

  feidee_file ||= Tempfile.new("feidee_sqlite", binmode: true)
  feidee_file.binmode
  feidee_file.write(private_header)
  feidee_file.write(sqlite.read)
  feidee_file.fsync
  feidee_file
end
trash_table_name(name) click to toggle source
# File lib/feidee_utils/database.rb, line 161
def trash_table_name name
  NoDeleteSuffixTables.each do |core_name|
    if name == "t_" + core_name then
      return "t_" + "deleted_" + core_name;
    end
  end

  name + "_delete"
end

Public Instance Methods

sqlite_backup(dest_file_path) click to toggle source
# File lib/feidee_utils/database.rb, line 47
def sqlite_backup(dest_file_path)
  self.execute("vacuum;")

  backup_sqlite_db = SQLite3::Database.new(dest_file_path.to_s)
  backup_obj = SQLite3::Backup.new(backup_sqlite_db, "main", self, "main")
  backup_obj.step(-1)
  backup_obj.finish
  backup_sqlite_db.close
end
validate_global_integrity() click to toggle source
# File lib/feidee_utils/database.rb, line 57
def validate_global_integrity
  @ledger.constants.each do |const|
    @ledger.const_get(const).validate_global_integrity if const != :Database
  end
end

Private Instance Methods

all_tables() click to toggle source
# File lib/feidee_utils/database.rb, line 64
    def all_tables
      rows = self.execute <<-SQL
        SELECT name FROM sqlite_master
        WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%'
        UNION ALL
        SELECT name FROM sqlite_temp_master
        WHERE type IN ('table','view')
        ORDER BY 1
      SQL
      rows.map do |row| row[0] end.sort
    end
drop_unused_tables() click to toggle source
# File lib/feidee_utils/database.rb, line 76
def drop_unused_tables
  useful_tables = Tables.values + PotentialUsefulTables
  useful_tables += useful_tables.map do |x|
    self.class.trash_table_name(x)
  end
  useful_tables.sort!

  # TODO: Record all tables droped.
  (all_tables - useful_tables).each do |table|
    self.execute("DROP TABLE IF EXISTS #{table}");
  end

  @missing_tables = Tables.values - all_tables
  if !@missing_tables.empty?
    raise "Missing tables: #{@missing_tables} from kbf backup."
  end
end
extract_metadata() click to toggle source
# File lib/feidee_utils/database.rb, line 94
def extract_metadata
  @platform = self.execute(
    "SELECT platform from #{Tables[:metadata]}"
  )[0][0];

  @ledger_name = self.get_first_row(
    "SELECT accountBookName FROM #{Tables[:profile]};"
  )[0];

  # This is not recorded in the database, so the lastest lastUpdateTime of
  # all transactions is chosen.
  timestamp = self.get_first_row(
    "SELECT max(lastUpdateTime) FROM #{Tables[:transactions]};"
  )[0]
  @last_modified_at = Time.at((timestamp or 0.0) / 1000)
end