Skip to content

Commit

Permalink
feat: overwrite data dir (+ maintenance)
Browse files Browse the repository at this point in the history
  • Loading branch information
JupiterPi committed Jan 24, 2025
1 parent dc63ccf commit d01335e
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 25 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ There are several ways to authenticate:

If you have 2FA enabled and don't specify a 2FA code, you will be prompted for it.

### Data directory

The data directory is where configuration files, credentials, cache etc. are stored and read from. By default, it is `%APPDATA%/filen-cli` (Windows), `~/Library/Application Support/filen-cli` (macOS) or `$XDG_CONFIG_HOME/filen-cli` or `~/.config/filen-cli` (Unix). If there is a directory named `.filen-cli` at the home directory `~`, it is used instead (for instance, the install script installs to this location). You can overwrite the location using the `--data-dir` flag or the `FILEN_CLI_DATA_DIR` environment variable.


## Access your Filen Drive

Expand Down Expand Up @@ -136,7 +140,7 @@ $ filen sync [sync pairs...] [--continuous]
Invoke `filen sync` to sync any locations with your Filen Drive. This is the same functionality you get with the Desktop app.

You must specify the sync pairs (`[sync pairs...]` above) as follows:
- **(central registry)** `filen sync`: Read the sync pairs from `$APP_DATA/filen_cli/syncPairs.json`.
- **(central registry)** `filen sync`: Read the sync pairs from `syncPairs.json` (inside the [data dir](#data-directory)).
This file must contain JSON of the type[^type] `{local: string, remote: string, syncMode: string, alias?: string, disableLocalTrash?: boolean, ignore?: string[], excludeDotFiles?: boolean}[]`.
`syncMode` can be `twoWay`, `localToCloud`, `localBackup`, `cloudToLocal` or `cloudBackup` (see [here](https://blog.filen.io/how-to-desktop-client/#:~:text=for%20this%20sync.-,Sync%20Modes,-%3A) on what that means). Note that since this is a JSON file, backslashes (`\`) in strings need to be escaped, e. g. `"C:\\some\\path"`).
- **(custom registry)** `filen sync <file>`: Read the sync pairs from a custom JSON file (same type as above).
Expand Down
5 changes: 3 additions & 2 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import FilenSDK, { APIError } from "@filen/sdk"
import { err, errExit, out, outVerbose, prompt, promptConfirm } from "../interface/interface"
import fsModule from "node:fs"
import { exists, platformConfigPath } from "../util/util"
import { exists } from "../util/util"
import path from "path"
import { CredentialsCrypto } from "./credentialsCrypto"
import { wrapRedTerminalText } from "../interface/util"
import { ANONYMOUS_SDK_CONFIG } from "../constants"
import { dataDir } from ".."

export type Credentials = {
email: string
Expand All @@ -20,7 +21,7 @@ export class Authentication {
private readonly filen: FilenSDK

private readonly crypto = new CredentialsCrypto()
private readonly credentialsDirectory = platformConfigPath()
private readonly credentialsDirectory = dataDir
private readonly credentialsFile = path.join(this.credentialsDirectory, ".credentials")
private readonly sdkConfigFile = ".filen-cli-auth-config"

Expand Down
4 changes: 2 additions & 2 deletions src/auth/credentialsCrypto.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import path from "path"
import fsModule from "node:fs"
import crypto from "node:crypto"
import { platformConfigPath } from "../util/util"
import { key } from "../buildInfo"
import { FilenSDKConfig } from "@filen/sdk"
import { errExit } from "../interface/interface"
import { dataDir } from ".."

/**
* Handles cryptography for securely storing credentials in a file.
Expand All @@ -17,7 +17,7 @@ export class CredentialsCrypto {
this.key = key

try {
const saltFile = path.join(platformConfigPath(), ".credentials.salt")
const saltFile = path.join(dataDir, ".credentials.salt")
if (!fsModule.existsSync(saltFile)) {
this.salt = crypto.randomBytes(32).toString("hex")
fsModule.writeFileSync(saltFile, this.salt)
Expand Down
4 changes: 2 additions & 2 deletions src/buildInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
export const version: string = "{{INJECT: VERSION}}"

// @ts-expect-error will be injected
export const disableUpdates: boolean = "{{INJECT: IS_CONTAINER}}"
export const isRunningAsContainer: boolean = "{{INJECT: IS_CONTAINER}}"

// @ts-expect-error will be injected
export const isRunningAsNPMPackage: boolean = "{{INJECT: IS_NPM_PACKAGE}}"
Expand All @@ -13,7 +13,7 @@ export const key: string = "{{INJECT: CRYPTO_BASE_KEY}}"

export function checkInjectedBuildInfo() {
return version !== "{{INJECT: VERSION}}"
&& (disableUpdates.toString() === "true" || disableUpdates.toString() === "false")
&& (isRunningAsContainer.toString() === "true" || isRunningAsContainer.toString() === "false")
&& (isRunningAsNPMPackage.toString() === "true" || isRunningAsNPMPackage.toString() === "false")
&& key !== "{{INJECT: CRYPTO_BASE_KEY}}"
}
7 changes: 4 additions & 3 deletions src/featureInterfaces/syncInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import pathModule from "path"
import { SyncMessage, SyncMode, SyncPair } from "@filen/sync/dist/types"
import { err, errExit, out, outVerbose, quiet } from "../interface/interface"
import fsModule, { PathLike } from "node:fs"
import { exists, platformConfigPath } from "../util/util"
import { exists } from "../util/util"
import getUuidByString from "uuid-by-string"
import { displayTransferProgressBar } from "../interface/util"
import { InterruptHandler } from "../interface/interrupt"
import os from "os"
import { dataDir } from ".."

export const syncOptions = {
"--continuous": Boolean,
Expand Down Expand Up @@ -51,7 +52,7 @@ const syncModeMappings = new Map<string, SyncMode>([
export class SyncInterface {
private readonly filen

private readonly defaultSyncPairsRegistry = pathModule.join(platformConfigPath(), "syncPairs.json")
private readonly defaultSyncPairsRegistry = pathModule.join(dataDir, "syncPairs.json")

constructor(filen: FilenSDK) {
this.filen = filen
Expand Down Expand Up @@ -99,7 +100,7 @@ export class SyncInterface {
const progressBar = continuous ? null : displayTransferProgressBar("Transferring", "files", 0)
const worker = new SyncWorker({
syncPairs: fullSyncPairs,
dbPath: pathModule.join(platformConfigPath(), "sync"),
dbPath: pathModule.join(dataDir, "sync"),
sdk: this.filen,
onMessage: msg => {
outVerbose(JSON.stringify(msg, null, 2))
Expand Down
17 changes: 15 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from "path"
import os from "os"
import { err, errExit, out, outVerbose, setOutputFlags, setupLogs } from "./interface/interface"
import { Authentication } from "./auth/auth"
import { checkInjectedBuildInfo, version } from "./buildInfo"
import { checkInjectedBuildInfo, isRunningAsContainer, isRunningAsNPMPackage, version } from "./buildInfo"
import { Updater } from "./updater"
import { HelpPage } from "./interface/helpPage"
import { FSInterface, fsOptions } from "./featureInterfaces/fs/fsInterface"
Expand All @@ -17,6 +17,7 @@ import { TrashInterface } from "./featureInterfaces/trashInterface"
import { PublicLinksInterface } from "./featureInterfaces/publicLinksInterface"
import { DriveMountingInterface } from "./featureInterfaces/driveMountingInterface"
import { ANONYMOUS_SDK_CONFIG } from "./constants"
import { determineDataDir } from "./util/util"

const args = arg(
{
Expand All @@ -42,6 +43,7 @@ const args = arg(
"-c": "--two-factor-code",

"--log-file": String,
"--data-dir": String,

"--skip-update": Boolean,
"--force-update": Boolean,
Expand All @@ -66,6 +68,11 @@ if (!checkInjectedBuildInfo()) {
*/
export const isDevelopment = args["--dev"] ?? false

/**
* The directory where data files (configuration files, cache, credentials etc.) are stored.
*/
export const dataDir = determineDataDir(args["--data-dir"])

// eslint-disable-next-line no-extra-semi
;(async () => {
if ((args["--version"] ?? false) || args["_"][0] === "version") {
Expand All @@ -77,7 +84,13 @@ export const isDevelopment = args["--dev"] ?? false
setupLogs(args["--log-file"])

outVerbose(`Filen CLI ${version}`)
if (isDevelopment) outVerbose("Running in development environment")

let environment = "Environment: "
environment += `data-dir=${dataDir}`
if (isRunningAsContainer) environment += ", in container"
if (isRunningAsNPMPackage) environment += ", as NPM package"
if (isDevelopment) environment += ", development"
outVerbose(environment)

if ((args["--help"] ?? false) || args["_"][0] === "help") {
const topic = (args["_"][0] === "help" ? args["_"][1] : args["_"][0])?.toLowerCase() ?? "general"
Expand Down
9 changes: 5 additions & 4 deletions src/updater.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { disableUpdates, isRunningAsNPMPackage, version } from "./buildInfo"
import { isRunningAsContainer, isRunningAsNPMPackage, version } from "./buildInfo"
import { err, errExit, out, outVerbose, promptYesNo } from "./interface/interface"
import path from "path"
import { spawn } from "node:child_process"
import { downloadFile, exists, platformConfigPath } from "./util/util"
import { downloadFile, exists } from "./util/util"
import * as fs from "node:fs"
import semver from "semver/preload"
import { dataDir } from "."

type UpdateCache = {
lastCheckedUpdate: number
Expand All @@ -26,7 +27,7 @@ type ReleaseInfo = {
* Manages updates.
*/
export class Updater {
private readonly updateCacheDirectory = platformConfigPath()
private readonly updateCacheDirectory = dataDir
private readonly updateCacheFile = path.join(this.updateCacheDirectory, "updateCache.json")
private readonly updateCheckExpiration = 10 * 60 * 1000 // check again after 10min

Expand Down Expand Up @@ -72,7 +73,7 @@ export class Updater {
const currentVersion = version
const latestVersion = latestRelease.tag_name

if (disableUpdates && latestDownloadUrl !== undefined && currentVersion !== latestVersion) {
if (isRunningAsContainer && latestDownloadUrl !== undefined && currentVersion !== latestVersion) {
// don't prompt for update in a container environment
out(`${(semver.gt(latestVersion, currentVersion) ? "Update available" : "Other version recommended")}: ${currentVersion} -> ${latestVersion}`)
return
Expand Down
21 changes: 12 additions & 9 deletions src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,14 @@ export async function directorySize(path: PathLike) {
return (await Promise.all(stats)).reduce((accumulator, { size }) => accumulator + size, 0)
}

let _platformConfigPath: string | undefined = undefined
/**
* Returns the platform-specific directory for storing configuration files.
* Determines the platform-specific directory for storing data files.
* Creates the directory if it doesn't exist.
* - Windows: `%APPDATA%\filen-cli`
* - OS X: `~/Library/Application Support/filen-cli` (or `~/.filen-cli`)
* - Unix: `$XDG_CONFIG_HOME/filen-cli` or `~/.config/filen-cli` (or `~/.filen-cli`)
* Default locations are: `%APPDATA%\filen-cli` (Windows), `~/Library/Application Support/filen-cli` (macOS), `$XDG_CONFIG_HOME/filen-cli` or `~/.config/filen-cli` (Unix).
* If it exists, `~/.filen-cli` is used instead.
* If the `--data-dir` flag or `FILEN_CLI_DATA_DIR` environment variable is set, its value is used instead.
*/
export function platformConfigPath(): string {
if (_platformConfigPath !== undefined) return _platformConfigPath

export function determineDataDir(dataDirFlag: string | undefined): string {
// default config path, see https://github.com/jprichardson/ospath/blob/master/index.js
let configPath: string = (() => {
switch (process.platform) {
Expand Down Expand Up @@ -94,13 +91,19 @@ export function platformConfigPath(): string {
configPath = pathModule.join(configPath, "dev")
}

if (dataDirFlag !== undefined) {
configPath = dataDirFlag
}
if (process.env.FILEN_CLI_DATA_DIR !== undefined) {
configPath = process.env.FILEN_CLI_DATA_DIR
}

if (!fsModule.existsSync(configPath)) {
fsModule.mkdirSync(configPath, {
recursive: true
})
}

_platformConfigPath = configPath
return configPath
}

Expand Down

0 comments on commit d01335e

Please sign in to comment.