repository-bridge
Version:
This module provides a simple CRUD API, enabling applications to normalize the use of different P2P repositories.
555 lines (483 loc) • 17.2 kB
JavaScript
// ---- ---- ---- ---- ----
//
// for JS, keep reference to what is being used by the client in this map
// this map stores wrapper instances
//
let g_supported_repos = require('../supported-repos')
/**
* Repository
*
* This class services calls from a client caller to one of the supported repositories.
*
* This class provides callers with a uniform interface to those repositories.
*
* new Repository({},['local','LAN'])
*
*/
class Repository {
constructor(config,kinds) {
this.configs = {} // for JS, keep reference to what is being used by the client in this map
this.repos = {}
this.repo_errors = []
this.desired_kinds = kinds
this.install_supported_repositories(config) // The config should include the repos the app wants to deal with
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
#extend_repository_support(support_update,replacements) {
for ( let ky in support_update ) {
if ( g_supported_repos[ky] === undefined ) {
g_supported_repos[ky] = support_update[ky] // add in the new wrapper instance
} else if ( replacements && replacements[ky] ) {
g_supported_repos[ky] = support_update[ky] // add in the new wrapper instance
}
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* install_supported_repositories
*
* Reloads the supported repositories.
*
* Currently the supported repositories are local (sandbox like)
*
* @param {object} conf
*/
install_supported_repositories(conf) {
//
if ( conf === undefined ) {
conf = this.configs // auto if not provided
}
//
try {
//
if ( typeof conf._app_add_support === 'object' ) {
this.#extend_repository_support(conf._app_add_support)
}
//
let kinds = this.desired_kinds
for ( let k of kinds ) { // keep the user application in control of what is loaded even when new repos are available...
if ( k in g_supported_repos ) { // only add configuration for when there is an instance
if ( k in conf ) {
this.configs[k] = conf[k] // add the configuration for the repository
} else {
this.configs[k] = {}
}
} else {
this.repo_errors.push(new Error(`unsupported repository type - ${k}`))
}
}
//
} catch (e) {
console.log(e)
}
//
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* update_supported_repositories
*
* Reloads the supported repositories.
*
* returns boolean -- true if successful
*
* @param {object} conf
*/
async update_supported_repositories(conf) {
if ( conf === undefined ) {
return false
}
//
let new_activations = Object.keys(conf._app_add_support)
if ( typeof conf._app_add_support === 'object' ) {
if ( typeof conf._replacements === 'object' ) {
this.#extend_repository_support(conf._app_add_support,conf._replacements)
} else {
this.#extend_repository_support(conf._app_add_support)
}
}
//
if ( (typeof conf._activate_kinds === 'object') && Array.isArray(conf._activate_kinds) ) {
new_activations = conf._activate_kinds
}
// otherwise everything
this.desired_kinds = this.desired_kinds.concat(new_activations)
for ( let k of new_activations ) { // keep the user application in control of what is loaded even when new repos are available...
if ( k in g_supported_repos ) { // only add configuration for when there is an instance
if ( k in conf ) {
this.configs[k] = conf[k] // add the configuration for the repository
} else if ( !( k in this.configs) ) {
this.configs[k] = {}
}
} else {
this.repo_errors.push(new Error(`unsupported repository type - ${k}`))
}
}
await this.init_newly_activated_repos(new_activations)
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* install_service_connection
*
* accommodate `com_link_manager` module.
*
* @param {object} instance - require wrapper instance to be interned into the bridge
* @param {object} conf - necessary configuration -- the repository client will most likely be activated
*/
async install_service_connection(instance,conf) {
//
if ( typeof conf._app_add_support === "object" ) {
for ( let ky in conf._app_add_support ) {
if ( conf._app_add_support[ky] === "add-wrapper-instance" ) {
conf._app_add_support[ky] = instance
break
}
}
}
//
await this.update_supported_repositories(conf)
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* update_service_connection
* @param {object} instance -- may be the instance of the wrapper, but this module does bookkeping on that
* @param {object} conf -- must have the activation information for the update
*/
async update_service_connection(instance,conf) {
// in this case instance should be this
await this.update_supported_repositories(conf)
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* init_repos
*
* perform necessary operations to ensure that repositories are running locally and that they have
* representative interfaces for accessing file operations.
*/
async init_repos() {
//
let promises = []
for ( let k in this.configs ) {
let conf = this.configs[k]
let factory = await g_supported_repos[k].import() // wait for the wrapper module to load the constructor
promises.push(g_supported_repos[k].init(conf,factory)) // async -- starting up repos will take longer
}
//
let connectors = await Promise.all(promises)
for ( let con of connectors ) {
let [key,node] = con
this.repos[key] = await node
}
//
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* init_newly_activated_repos
*
* @param {Array} new_activations -- a list of keys for pulling configurations out of the configuratios map.
*/
async init_newly_activated_repos(new_activations) {
//
let promises = []
for ( let k of new_activations ) {
let conf = this.configs[k]
let factory = await g_supported_repos[k].import() // wait for the wrapper module to load the constructor
promises.push(g_supported_repos[k].init(conf,factory)) // async -- starting up repos will take longer
}
//
let connectors = await Promise.all(promises)
for ( let con of connectors ) {
let [key,node] = con
this.repos[key] = await node // the wrapper that has direct access to the vendor client instance
}
//
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
async ready(kind) {
try {
let w_repo = this.repos[kind]
if ( w_repo && typeof w_repo.ready === 'function' ) {
let result = await w_repo.ready()
return (result === undefined) ? true : result
}
} catch (e) {
console.log(e)
}
return false
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* store
*
* In some repositories, this is referred to as `pin`
*
* local storage of an object performed by pulling it in from the P2P storage
* given the object hash id.
*
* Obtains the ID for the repository indicated in `protocol`. Uses the ID to tell the repository
* to find the object, load it into the local persistent storage, and signals (or peforms) protection of
* the object from erasure.
*
* The assumption with `store_local` is that a client will already have the meta descriptor of the file.
* Hence, it has access to the pin ID.
*
* Required fields of the entry_obj, the parameter:
*
* * protocol - this is the type of repository the command is using
* * <protocol id> - a field keyed by the value in `protocol`
*
* @param {object} entry_obj - an object that will be stored in the repoistory
* @returns promise -- resolves to true if the pin succeeds. false if an error occurs.
*/
async store(entry_obj) { // add entry object to repo
try {
let repo_ky = entry_obj.protocol
if ( repo_ky === undefined ) return
let w_repo = this.repos[repo_ky]
if ( w_repo === undefined ) return
let pin_id = entry_obj[repo_ky] // this is the repo based id of the object (used to find the object within the P2P system)
if ( pin_id === undefined ) return
await w_repo.store_local(pin_id)
return true
} catch (e) {
return false
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* store_pin
*
* @param {string} kind
* @param {string} pin_id
*/
async store_pin(kind,pin_id) {
let w_repo = this.repos[kind]
if ( w_repo === undefined ) return
await w_repo.store_local(pin_id)
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
*
* remove
*
* Reverses the operation of `store`. The object might not be removed from the entirity of
* distributed storage systems. But, this method removes the restriction from doing so.
*
* @param {object} entry_obj - the meta object (may be a view of the added object)
* @returns
*/
async remove(entry_obj) {
try {
let repo_ky = entry_obj.protocol
if ( repo_ky === undefined ) return
let w_repo = this.repos[repo_ky]
if ( w_repo === undefined ) return
let repo_id = entry_obj[repo_ky]
if ( repo_id === undefined ) return
await w_repo.rm_local(repo_id) // remove local using the hash of the object...
} catch (e) {
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* remove_pin
*
* @param {string} kind
* @param {string} pin_id
*/
async remove_pin(kind,pin_id) {
let w_repo = this.repos[kind]
if ( w_repo === undefined ) return
await w_repo.rm_local(pin_id)
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* Replaces objects stored locally
*
* Operates on the local `pinned` data objects.
*
* @param {object} old_obj
* @param {object} new_obj
*/
async replace(old_obj,new_obj) {
await this.remove(old_obj)
await this.store(new_obj)
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* add
*
* Add an object in the sense that it is added in ipfs or in the sense that
* a torrent is created and publicized in bittorrent. That is, it adds the
* object to the storage medium and makes the item accessible to all, but does not
* require the object to be protected from erasure.
*
* Returns a descriptor with (or that is) the ID of the object relative to the storage platform.
*
*
* @param {string} kind - which kind of repo is this? ipfs, tor, torv2
* @param {object} object - an object to be stored in the repo (raw form)
* @returns A storage ID as a string
*/
async add(kind,object) {
try {
let w_repo = this.repos[kind]
const repo_record = await w_repo.add(object)
if ( typeof repo_record === 'string' ) {
return repo_record
} else {
let repo_id_str = g_supported_repos[kind].stringify(repo_record)
return repo_id_str
}
} catch (e) {
console.log(e)
}
return false
}
//
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* add_file
*
* Similar to `add` but knows that the object that is passed is actually a file.
* Will return false after logging any errors if the object is not a file.
*
* @param {*} kind
* @param {*} object
* @returns
*/
async add_file(kind,object) {
try {
let w_repo = this.repos[kind]
const repo_record = await w_repo.add_file(object)
if ( typeof repo_record === 'string' ) {
return repo_record
} else {
let repo_id_str = g_supported_repos[kind].stringify(repo_record)
return repo_id_str
}
} catch (e) {
console.log(e)
}
return false
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* fetch
*
* Given the type of repo, this maps a unique id of an object relative to the repo and returns the object
*
* @param {string} kind
* @param {string} id
* @returns Object - the object stored in the repo.
*/
async fetch(kind,id) {
try {
let w_repo = this.repos[kind]
let content = await g_supported_repos[kind].fetch(id,w_repo.repo())
return content
} catch (e) {
console.log(e)
}
return false
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* ls
*
* returns system stat info about the object, relative to the repo,
* where the object is identified by its ID previously returned by an `add` operation.
*
* @param {*} kind
* @param {*} cid
* @returns
*/
async * ls(kind,cid) {
try {
let w_repo = this.repos[kind]
yield * await g_supported_repos[kind].ls(cid,w_repo.repo())
return
} catch (e) {
console.log(e)
}
yield false
return
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* cat
*
* Similar to fetch, but returns the file object as a stream.
*
* Given the type of repo, this maps a unique id of an object relative to the repo and returns the object as a stream.
*
* @param {string} kind
* @param {string} id
* @returns Object - the object stored in the repo.
*/
async * cat(kind,cid) {
try {
let w_repo = this.repos[kind]
yield * await g_supported_repos[kind].cat(cid,w_repo.repo())
return
} catch (e) {
console.log(e)
}
yield false
return
}
/**
* set_link_manager
*
* @param {string} kind
* @param {object} conf -- configuration for the link constructor
* @param {object} link_m -- instance of a link manager..
*
* @returns boolean
*/
set_link_manager(kind,conf,link_m) {
try {
let w_repo = this.repos[kind]
if ( typeof w_repo.set_link_manager === 'function' ) {
w_repo.set_link_manager(conf,link_m)
}
return true
} catch (e) {
console.log(e)
}
return false
}
set_link_mamagers(conf,link_m) {
for ( let k in this.configs ) {
if ( k in conf ) {
this.set_link_manager(k,conf,link_m)
}
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* diagnotistic
*
* @param {string} kind
* @param {string} which - the name of the diagnostic to run
* @returns
*/
async diagnotistic(kind,which) {
try {
let w_repo = this.repos[kind]
let content = await g_supported_repos[kind].diagnotistic(which,w_repo.repo())
return content
} catch (e) {
console.log(e)
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
/**
* get_repo_errors
*
* @returns Array - an array of errors collected during operation
*/
get_repo_errors() {
return this.repo_errors
}
}
module.exports = Repository