module Pigeon class Database attr_reader :who_am_i def initialize(path: PIGEON_DB_PATH) @store = Pigeon::Storage.new(path: path) init_ident end # === PEERS def add_peer(p) store.add_peer(p) end def block_peer(p) store.block_peer(p) end def remove_peer(p) store.remove_peer(p) end def peer_blocked?(p) store.peer_blocked?(p) end def all_blocks store.all_blocks end def all_peers store.all_peers end # === MESSAGES def all_messages(mhash = nil) store.all_messages(mhash) end def message_saved?(multihash) store.message_saved?(multihash) end def _save_message(msg_obj) store.insert_message(Helpers.verify_message(self, msg_obj)) end def read_message(multihash) store.read_message(multihash) end def get_message_count_for(multihash) store.get_message_count_for(multihash) end def get_message_by_depth(multihash, depth) store.get_message_by_depth(multihash, depth) end def add_message(kind, params) publish_draft(new_draft(kind: kind, body: params)) end # Store a message that someone (not the LocalIdentity) # has authored. def _ingest_message(author:, body:, depth:, kind:, lipmaa:, prev:, signature:) msg = Message.new(author: RemoteIdentity.new(author), kind: kind, body: body, prev: prev, lipmaa: lipmaa, signature: signature, depth: depth) _save_message(msg) end # === DRAFTS def reset_draft add_config(CURRENT_DRAFT, nil) end def new_draft(kind:, body: {}) old = get_config(CURRENT_DRAFT) raise "PUBLISH OR RESET CURRENT DRAFT (#{old.kind}) FIRST" if old save_draft(Draft.new(kind: kind, body: body)) end def save_draft(draft) add_config(CURRENT_DRAFT, draft) draft end def get_draft draft = store.get_config(CURRENT_DRAFT) unless draft raise "THERE IS NO DRAFT. CREATE ONE FIRST." end draft end def update_draft(k, v) Helpers.update_draft(self, k, v) end def reset_draft add_config(CURRENT_DRAFT, nil) end # Author a new message. def publish_draft(draft = get_draft) Helpers.publish_draft(self, draft) end # === BUNDLES def export_bundle(file_path = DEFAULT_BUNDLE_PATH) Helpers.mkdir_p(file_path) # Fetch messages for all peers peers = all_peers + [who_am_i.multihash] messages = peers.map do |peer| all_messages(peer) .map { |multihash| read_message(multihash) } .sort_by(&:depth) end.flatten # Attach blobs for all messages in bundle. messages .map(&:collect_blobs) .flatten .uniq .map do |mhash| hash2filepath = Helpers.hash2file_path(mhash) blob_path = File.join(file_path, hash2filepath) blob = get_blob(mhash) puts "=== EXPORT" puts " blob_path: #{blob_path}" puts " mhash: #{mhash}" puts " blob: #{blob.length} bytes" Helpers.write_to_disk(blob_path, mhash, blob) end # Render messages for all peers. content = messages .map(&:render) .join(BUNDLE_MESSAGE_SEPARATOR) File.write(File.join(file_path, "gossip.pgn"), content + CR) end def import_bundle(file_path = DEFAULT_BUNDLE_PATH) bundle = File.read(File.join(file_path, "gossip.pgn")) tokens = Pigeon::Lexer.tokenize(bundle) blobs = tokens.each_with_object(Set.new) do |(_a, b, c), set| [b, c].map do |d| set.add(d) if Helpers.blob_multihash?(d) end end.map do |multihash| binding.pry unless store.have_blob?(multihash) end Pigeon::Parser.parse(self, tokens) end # === BLOBS def get_blob(b) store.get_blob(b) end def add_blob(b) store.add_blob(b) end # === DB Management def get_config(k) store.get_config(k) end def add_config(k, v) store.add_config(k, v) end def reset_database store.reset init_ident end private attr_reader :store def init_ident secret = get_config(SEED_CONFIG_KEY) if secret @who_am_i = LocalIdentity.new(secret) else new_seed = SecureRandom.random_bytes(Ed25519::KEY_SIZE) add_config(SEED_CONFIG_KEY, new_seed) @who_am_i = LocalIdentity.new(new_seed) end end end end