Skip to content

Commit

Permalink
WIP - #305 EOD commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tegefaulkes committed Feb 16, 2022
1 parent 5562d92 commit 8cc6c29
Show file tree
Hide file tree
Showing 2 changed files with 316 additions and 286 deletions.
213 changes: 194 additions & 19 deletions src/vaults/VaultInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import type { EncryptedFS } from 'encryptedfs';
import type { DB, DBDomain, DBLevel } from '@matrixai/db';
import type {
VaultId,
VaultName,
VaultRef,
CommitId,
CommitLog,
FileSystemReadable,
FileSystemWritable,
FileSystemWritable, VaultIdEncoded,
} from './types';
import type { KeyManager } from '../keys';
import type { NodeId } from '../nodes/types';
import type { NodeId, NodeIdEncoded } from '../nodes/types';
import type { NodeConnectionManager } from '../nodes';
import type { ResourceAcquire } from '../utils';
import path from 'path';
import git from 'isomorphic-git';
Expand All @@ -20,9 +22,22 @@ import {
CreateDestroyStartStop,
ready,
} from '@matrixai/async-init/dist/CreateDestroyStartStop';
import { IdInternal, utils as idUtils } from '@matrixai/id';
import * as vaultsUtils from './utils';
import * as vaultsErrors from './errors';
import { withF, withG } from '../utils';
import { utils as nodesUtils } from '../nodes';

// TODO: Update creation of metadata.
// the use of the remote field needs to be updated so that it is using the
// remote location instead of a boolean. the remote also needs to be proerly
// set, likely after calling `start()` in cloneVaultInternal.

// TODO: this might be temp?
export type RemoteInfo = {
remoteNode: NodeIdEncoded;
remoteVault: VaultIdEncoded;
};

interface VaultInternal extends CreateDestroyStartStop {}
@CreateDestroyStartStop(
Expand All @@ -32,22 +47,22 @@ interface VaultInternal extends CreateDestroyStartStop {}
class VaultInternal {
public static async createVaultInternal({
vaultId,
vaultName,
db,
vaultsDb,
vaultsDbDomain,
keyManager,
efs,
remote = false,
logger = new Logger(this.name),
fresh = false,
}: {
vaultId: VaultId;
vaultName?: VaultName;
db: DB;
vaultsDb: DBLevel;
vaultsDbDomain: DBDomain;
keyManager: KeyManager;
efs: EncryptedFS;
remote?: boolean;
logger?: Logger;
fresh?: boolean;
}): Promise<VaultInternal> {
Expand All @@ -62,34 +77,37 @@ class VaultInternal {
efs,
logger,
});
vault.vaultName = vaultName
await vault.start({ fresh });
logger.info(`Created ${this.name} - ${vaultIdEncoded}`);
return vault;
}

public static async cloneVaultInternal({
targetNodeId,
targetVaultNameOrId,
vaultId,
db,
vaultsDb,
vaultsDbDomain,
keyManager,
nodeConnectionManager,
efs,
logger = new Logger(this.name),
}: {
targetNodeId: NodeId;
targetVaultNameOrId: VaultId | VaultName;
vaultId: VaultId;
db: DB;
vaultsDb: DBLevel;
vaultsDbDomain: DBDomain;
efs: EncryptedFS;
keyManager: KeyManager;
remote?: boolean;
nodeConnectionManager: NodeConnectionManager;
logger?: Logger;
}): Promise<VaultInternal> {
const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId);
logger.info(`Cloning ${this.name} - ${vaultIdEncoded}`);
// TODO:
// Perform the cloning operation to preseed state
// and also seed the remote state
const vault = new VaultInternal({
vaultId,
db,
Expand All @@ -99,11 +117,59 @@ class VaultInternal {
efs,
logger,
});
// This error flag will contain the error returned by the cloning grpc stream
let error;
// Let vaultName, remoteVaultId;
const thisNodeId = keyManager.getNodeId();

// Make the directory where the .git files will be auto generated and
// where the contents will be cloned to ('contents' file)
await efs.mkdir(vault.vaultDataDir, { recursive: true });
try {
const [vaultName, remoteVaultId] = await nodeConnectionManager.withConnF(
targetNodeId,
async (connection) => {
const client = connection.getClient();
const [request, vaultName, remoteVaultId] = await vaultsUtils.request(
client,
thisNodeId,
targetVaultNameOrId,
);
await git.clone({
fs: efs,
http: { request },
dir: vault.vaultDataDir,
gitdir: vault.vaultGitDir,
url: 'http://',
singleBranch: true,
});
return [vaultName, remoteVaultId];
}
);
// Fixme, handle name conflict;
vault.vaultName = vaultName;
vault.remote = {
remoteNode: nodesUtils.encodeNodeId(targetNodeId),
remoteVault: vaultsUtils.encodeVaultId(remoteVaultId),
}
} catch (err) {
// If the error flag set and we have the generalised SmartHttpError from
// isomorphic git then we need to throw the polykey error
if (err instanceof git.Errors.SmartHttpError && error) {
throw error;
}
throw err;
}

await vault.start();
logger.info(`Cloned ${this.name} - ${vaultIdEncoded}`);
return vault;
}

static dirtyKey = 'dirty';
static remoteKey = 'remote';
static nameKey = 'key';

public readonly vaultId: VaultId;
public readonly vaultIdEncoded: string;
public readonly vaultDataDir: string;
Expand All @@ -116,9 +182,11 @@ class VaultInternal {
protected vaultDbDomain: DBDomain;
protected vaultDb: DBLevel;
protected keyManager: KeyManager;
protected vaultsNamesDomain: DBDomain;
protected efs: EncryptedFS;
protected efsVault: EncryptedFS;
protected remote: boolean;
protected vaultName: VaultName | undefined;
protected remote: RemoteInfo | undefined;
protected _lock: Mutex = new Mutex();

public lock: ResourceAcquire<Mutex> = async () => {
Expand Down Expand Up @@ -165,8 +233,14 @@ class VaultInternal {
`Starting ${this.constructor.name} - ${this.vaultIdEncoded}`,
);
const vaultDbDomain = [...this.vaultsDbDomain, this.vaultIdEncoded];
const vaultsNamesDomain = [...this.vaultsDbDomain, 'names'];
const vaultDb = await this.db.level(this.vaultIdEncoded, this.vaultsDb);
// Let's backup any metadata.
this.vaultName = await this.db.get<VaultName>(this.vaultDbDomain, VaultInternal.nameKey) ?? this.vaultName;
if (this.vaultName == null) throw Error(`Temp error, can't have undefined vaultName`);

if (fresh) {
// TODO: remove name->id mapping from VaultManager.
await vaultDb.clear();
try {
await this.efs.rmdir(this.vaultIdEncoded, {
Expand All @@ -185,6 +259,7 @@ class VaultInternal {
await this.setupGit();
const efsVault = await this.efs.chroot(this.vaultDataDir);
this.vaultDbDomain = vaultDbDomain;
this.vaultsNamesDomain = vaultsNamesDomain;
this.vaultDb = vaultDb;
this.efsVault = efsVault;
this.logger.info(
Expand Down Expand Up @@ -310,7 +385,7 @@ class VaultInternal {
f: (fs: FileSystemWritable) => Promise<void>,
): Promise<void> {
return withF([this.lock], async () => {
await this.db.put(this.vaultsDbDomain, 'dirty', true);
await this.db.put(this.vaultsDbDomain, VaultInternal.dirtyKey, true);
// This should really be an internal property
// get whether this is remote, and the remote address
// if it is, we consider this repo an "attached repo"
Expand All @@ -326,7 +401,7 @@ class VaultInternal {

await f(this.efsVault);

await this.db.put(this.vaultsDbDomain, 'dirty', false);
await this.db.put(this.vaultsDbDomain, VaultInternal.dirtyKey, false);
});

// Const message: string[] = [];
Expand Down Expand Up @@ -492,28 +567,128 @@ class VaultInternal {
});
}

@ready(new vaultsErrors.ErrorVaultNotRunning())
public async pullVault({
nodeConnectionManager,
pullNodeId,
pullVaultNameOrId,
}: {
nodeConnectionManager: NodeConnectionManager;
pullNodeId?: NodeId;
pullVaultNameOrId?: VaultId | VaultName;
}) {
// This error flag will contain the error returned by the cloning grpc stream
let error;
// Keeps track of whether the metadata needs changing to avoid unnecessary db ops
// 0 = no change, 1 = change with vault Id, 2 = change with vault name
let metaChange = 0;
const thisNodeId = this.keyManager.getNodeId();
// TODO: get proper metadata.
let remoteNode = {} as NodeId;
let remoteVault = '';

if (pullNodeId == null) {
pullNodeId = remoteNode;
} else {
metaChange = 1;
remoteNode = pullNodeId;
}
if (pullVaultNameOrId == null) {
pullVaultNameOrId = vaultsUtils.decodeVaultId(remoteVault!);
} else {
metaChange = 1;
if (typeof pullVaultNameOrId === 'string') {
metaChange = 2;
} else {
remoteVault = pullVaultNameOrId.toString();
}
}
this.logger.info(
`Pulling Vault ${vaultsUtils.encodeVaultId(
this.vaultId,
)} from Node ${pullNodeId}`,
);
let remoteVaultId: VaultIdEncoded;
try {
remoteVaultId = await nodeConnectionManager.withConnF(pullNodeId!, async (connection) => {
const client = connection.getClient();
const [request, , remoteVaultId] = await vaultsUtils.request(
client,
thisNodeId,
pullVaultNameOrId!,
);
await git.pull({
fs: this.efs,
http: { request },
dir: this.vaultDataDir,
gitdir: this.vaultGitDir,
url: `http://`,
ref: 'HEAD',
singleBranch: true,
author: {
name: nodesUtils.encodeNodeId(pullNodeId!),
},
});
return remoteVaultId;
});
} catch (err) {
// If the error flag set and we have the generalised SmartHttpError from
// isomorphic git then we need to throw the polykey error
if (err instanceof git.Errors.SmartHttpError && error) {
throw error;
} else if (err instanceof git.Errors.MergeNotSupportedError) {
throw new vaultsErrors.ErrorVaultsMergeConflict(
'Merge Conflicts are not supported yet',
);
}
throw err;
}
if (metaChange !== 0) {
if (metaChange === 2) remoteVault = remoteVaultId;
// TODO: update remote metadata here.
}
this.logger.info(
`Pulled Vault ${vaultsUtils.encodeVaultId(
this.vaultId,
)} from Node ${pullNodeId}`,
);
}

/**
* Setup the vault metadata
*/
protected async setupMeta(): Promise<void> {
protected async setupMeta(
): Promise<void> {
// Setup the vault metadata
// setup metadata
// and you need to make certain preparations
// the meta gets created first
// if the SoT is the database
// are we suposed to check this?
// are we supposed to check this?

if ((await this.db.get<boolean>(this.vaultDbDomain, 'remote')) == null) {
await this.db.put(this.vaultDbDomain, 'remote', true);
// If this is not existing
// Update remote
if (this.remote != null){
await this.db.put(this.vaultDbDomain, VaultInternal.remoteKey, this.remote);
}

// If this is not existing
// setup default vaults db
await this.db.get<boolean>(this.vaultsDbDomain, 'dirty');
if (await this.db.get<boolean>(this.vaultDbDomain, VaultInternal.dirtyKey) == null) {
await this.db.put(this.vaultsDbDomain, VaultInternal.dirtyKey, true);
}

// Set up vault Name
if (await this.db.get<string>(this.vaultDbDomain, VaultInternal.nameKey) == null && this.vaultName != null) {
await this.db.put(this.vaultsDbDomain, VaultInternal.nameKey, this.vaultName);
}

// Setting reverse mapping.
if (this.vaultName != null && await this.db.get<string>(this.vaultsNamesDomain, this.vaultName) == null) {
await this.db.put(this.vaultsNamesDomain, this.vaultName, this.vaultIdEncoded);
}

// Remote: [NodeId, VaultId] | undefined
// dirty: boolean
// name: string
// name: string | undefined
}

/**
Expand Down
Loading

0 comments on commit 8cc6c29

Please sign in to comment.