Skip to content

Commit

Permalink
refactor: migrate wallet from .afj to .credo
Browse files Browse the repository at this point in the history
Signed-off-by: Sai Ranjit Tummalapalli <sairanjit.tummalapalli@ayanworks.com>
  • Loading branch information
sairanjit committed Jan 28, 2025
1 parent 70c849d commit 57fd858
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/core/src/storage/FileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export interface FileSystem {
read(path: string): Promise<string>
delete(path: string): Promise<void>
downloadToFile(url: string, path: string, options?: DownloadToFileOptions): Promise<void>
migrateWalletToCredoFolder(walletId: string): Promise<void>
}
6 changes: 6 additions & 0 deletions packages/core/src/storage/migration/updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { updateV0_2ToV0_3 } from './updates/0.2-0.3'
import { updateV0_3ToV0_3_1 } from './updates/0.3-0.3.1'
import { updateV0_3_1ToV0_4 } from './updates/0.3.1-0.4'
import { updateV0_4ToV0_5 } from './updates/0.4-0.5'
import { updateV0_5ToV0_6 } from './updates/0.5-0.6'

export const INITIAL_STORAGE_VERSION = '0.1'

Expand Down Expand Up @@ -52,6 +53,11 @@ export const supportedUpdates = [
toVersion: '0.5',
doUpdate: updateV0_4ToV0_5,
},
{
fromVersion: '0.5',
toVersion: '0.6',
doUpdate: updateV0_5ToV0_6,
},
] as const

// Current version is last toVersion from the supported updates
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/storage/migration/updates/0.5-0.6/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { BaseAgent } from '../../../../agent/BaseAgent'

import { migrateToCredoFolder } from './migrateToCredoFolder'

export async function updateV0_5ToV0_6<Agent extends BaseAgent>(agent: Agent): Promise<void> {
await migrateToCredoFolder(agent)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { BaseAgent } from '../../../../agent/BaseAgent'
import type { FileSystem } from '../../../FileSystem'

import { InjectionSymbols } from '../../../../constants'
import { CredoError } from '../../../../error'

/**
* Migrates the sqlite folder location from .afj to .credo in the storage directory in node and react native.
*
*/
export async function migrateToCredoFolder<Agent extends BaseAgent>(agent: Agent) {
const walletId = agent.config.walletConfig?.id

if (!walletId) {
throw new CredoError('Wallet id is required to migrate the wallet to .credo')
}

// Adding type assertion to get the storage config
const storageConfig = agent.config.walletConfig?.storage as {
config?: { inMemory?: boolean }
}

// If no storage config is provided, we set default as sqlite
// https://github.com/openwallet-foundation/credo-ts/blob/main/packages/askar/src/utils/askarWalletConfig.ts#L35
// and we only migrate the data folder if the storage config is not set to inMemory
if (!storageConfig || (storageConfig.config && !storageConfig.config?.inMemory)) {
return
}

agent.config.logger.info('Migrating data from .afj to .credo')

const fileSystem = agent.dependencyManager.resolve<FileSystem>(InjectionSymbols.FileSystem)

await fileSystem.migrateWalletToCredoFolder(walletId)
agent.config.logger.info('Migration completed successfully')
}
60 changes: 52 additions & 8 deletions packages/node/src/NodeFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import https from 'https'
import { tmpdir, homedir } from 'os'
import { dirname } from 'path'

const { access, readFile, writeFile, mkdir, rm, unlink, copyFile } = promises
const { access, readFile, writeFile, mkdir, rm, unlink, copyFile, cp } = promises

export class NodeFileSystem implements FileSystem {
public readonly dataPath
Expand All @@ -19,16 +19,61 @@ export class NodeFileSystem implements FileSystem {
* Create new NodeFileSystem class instance.
*
* @param baseDataPath The base path to use for reading and writing data files used within the framework.
* Files will be created under baseDataPath/.afj directory. If not specified, it will be set to homedir()
* Files will be created under baseDataPath/.credo directory. If not specified, it will be set to homedir()
* @param baseCachePath The base path to use for reading and writing cache files used within the framework.
* Files will be created under baseCachePath/.afj directory. If not specified, it will be set to homedir()
* Files will be created under baseCachePath/.credo directory. If not specified, it will be set to homedir()
* @param baseTempPath The base path to use for reading and writing temporary files within the framework.
* Files will be created under baseTempPath/.afj directory. If not specified, it will be set to tmpdir()
* Files will be created under baseTempPath/.credo directory. If not specified, it will be set to tmpdir()
*/
public constructor(options?: { baseDataPath?: string; baseCachePath?: string; baseTempPath?: string }) {
this.dataPath = options?.baseDataPath ? `${options?.baseDataPath}/.afj` : `${homedir()}/.afj/data`
this.cachePath = options?.baseCachePath ? `${options?.baseCachePath}/.afj` : `${homedir()}/.afj/cache`
this.tempPath = `${options?.baseTempPath ?? tmpdir()}/.afj`
this.dataPath = options?.baseDataPath ? `${options?.baseDataPath}/.credo` : `${homedir()}/.credo/data`
this.cachePath = options?.baseCachePath ? `${options?.baseCachePath}/.credo` : `${homedir()}/.credo/cache`
this.tempPath = `${options?.baseTempPath ?? tmpdir()}/.credo`
}

/**
* Migrate data from .afj to .credo if .afj exists.
* Copy the contents from old directory (.afj) to new directory (.credo).
*/
public async migrateWalletToCredoFolder(walletId: string) {
try {
// We only migrate the specific wallet folder because other wallets might be using the same .afj folder
// which are used by different agents
const oldWalletPath = this.dataPath.replace('.credo/data', `.afj/data/wallet/${walletId}`)
const cacheAfjPath = this.cachePath.replace('.credo', '.afj')
const tempAfjPath = this.tempPath.replace('.credo', '.afj')

const pathsToMigrate = [
{
from: oldWalletPath,
// We manually construct the path to the wallet folder because we only want to migrate the specific wallet
to: this.dataPath + '/wallet/' + walletId,
},
{
from: cacheAfjPath,
to: this.cachePath,
},
{
from: tempAfjPath,
to: this.tempPath,
},
]

for await (const path of pathsToMigrate) {
// Migrate if the old paths exist
if (await this.exists(path.from)) {
await this.copyDirectory(path.from, path.to)
}
}
} catch (error) {
throw new CredoError(`Error during migration from .afj to .credo`, {
cause: error,
})
}
}

public async copyDirectory(sourcePath: string, destinationPath: string) {
await cp(sourcePath, destinationPath, { recursive: true })
}

public async exists(path: string) {
Expand All @@ -49,7 +94,6 @@ export class NodeFileSystem implements FileSystem {
}

public async write(path: string, data: string): Promise<void> {
// Make sure parent directories exist
await mkdir(dirname(path), { recursive: true })

return writeFile(path, data, { encoding: 'utf-8' })
Expand Down
13 changes: 13 additions & 0 deletions packages/node/tests/NodeFileSystem.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TypedArrayEncoder } from '@credo-ts/core'
import nock, { cleanAll, enableNetConnect } from 'nock'
import { homedir } from 'os'
import path from 'path'

import { NodeFileSystem } from '../src/NodeFileSystem'
Expand Down Expand Up @@ -38,5 +39,17 @@ describe('@credo-ts/file-system-node', () => {
)
})
})

describe('migrateWalletToCredoFolder', () => {
test('should copy the files from .afj to .credo', async () => {
const filePath = `${homedir()}/.afj/data/wallet/test/testwallet.db`

await fileSystem.write(filePath, 'some-random-content')

await fileSystem.migrateWalletToCredoFolder('test')

expect(await fileSystem.exists(`${fileSystem.dataPath}/wallet/test/testwallet.db`)).toBe(true)
})
})
})
})
78 changes: 70 additions & 8 deletions packages/react-native/src/ReactNativeFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,88 @@ export class ReactNativeFileSystem implements FileSystem {
* Create new ReactNativeFileSystem class instance.
*
* @param baseDataPath The base path to use for reading and writing data files used within the framework.
* Files will be created under baseDataPath/.afj directory. If not specified, it will be set to
* Files will be created under baseDataPath/.credo directory. If not specified, it will be set to
* RNFS.DocumentDirectoryPath
* @param baseCachePath The base path to use for reading and writing cache files used within the framework.
* Files will be created under baseCachePath/.afj directory. If not specified, it will be set to
* Files will be created under baseCachePath/.credo directory. If not specified, it will be set to
* RNFS.CachesDirectoryPath
* @param baseTempPath The base path to use for reading and writing temporary files within the framework.
* Files will be created under baseTempPath/.afj directory. If not specified, it will be set to
* Files will be created under baseTempPath/.credo directory. If not specified, it will be set to
* RNFS.TemporaryDirectoryPath
*
* @see https://github.com/itinance/react-native-fs#constants
*/
public constructor(options?: { baseDataPath?: string; baseCachePath?: string; baseTempPath?: string }) {
this.dataPath = `${options?.baseDataPath ?? RNFS.DocumentDirectoryPath}/.afj`
this.dataPath = `${options?.baseDataPath ?? RNFS.DocumentDirectoryPath}/.credo`
// In Android, TemporaryDirectoryPath falls back to CachesDirectoryPath
this.cachePath = options?.baseCachePath
? `${options?.baseCachePath}/.afj`
: `${RNFS.CachesDirectoryPath}/.afj${Platform.OS === 'android' ? '/cache' : ''}`
? `${options?.baseCachePath}/.credo`
: `${RNFS.CachesDirectoryPath}/.credo${Platform.OS === 'android' ? '/cache' : ''}`
this.tempPath = options?.baseTempPath
? `${options?.baseTempPath}/.afj`
: `${RNFS.TemporaryDirectoryPath}/.afj${Platform.OS === 'android' ? '/temp' : ''}`
? `${options?.baseTempPath}/.credo`
: `${RNFS.TemporaryDirectoryPath}/.credo${Platform.OS === 'android' ? '/temp' : ''}`
}

/**
* Migrate data from .afj to .credo if .afj exists.
* Copy the contents from old directory (.afj) to new directory (.credo).
*/
public async migrateWalletToCredoFolder() {
try {
const oldDataPath = this.dataPath.replace('.credo', `.afj`)
const cacheAfjPath = this.cachePath.replace('.credo', '.afj')
const tempAfjPath = this.tempPath.replace('.credo', '.afj')

const pathsToMigrate = [
{
from: oldDataPath,
to: this.dataPath,
},
{
from: cacheAfjPath,
to: this.cachePath,
},
{
from: tempAfjPath,
to: this.tempPath,
},
]

for await (const path of pathsToMigrate) {
// Migrate if the old paths exist
if (await this.exists(path.from)) {
await this.copyDirectory(path.from, path.to)
}
}
} catch (error) {
throw new CredoError(`Error during migration from .afj to .credo`, {
cause: error,
})
}
}

public async copyDirectory(sourcePath: string, destinationPath: string) {
try {
// Ensure the target directory exists
await RNFS.mkdir(destinationPath)

// Get the contents of the source directory
const contents = await RNFS.readDir(sourcePath)

for (const item of contents) {
const newPath = `${destinationPath}/${item.name}`

if (item.isDirectory()) {
// Recursively copy subdirectories
await this.copyDirectory(item.path, newPath)
} else {
// Copy files to the new location
await RNFS.copyFile(item.path, newPath)
}
}
} catch (error) {
throw new CredoError(`Error copying directory from ${sourcePath} to ${destinationPath}`, { cause: error })
}
}

public async exists(path: string): Promise<boolean> {
Expand Down

0 comments on commit 57fd858

Please sign in to comment.