Skip to content

Commit c69c729

Browse files
authored
(feat): uuid translation for given name (#49)
* (feat): translation partially implemented * (feat): uuid translation for latest full working * (feat): uuid translation for restore fixed * (docs): updated README with changes from this PR
1 parent a7bb71f commit c69c729

File tree

8 files changed

+133
-48
lines changed

8 files changed

+133
-48
lines changed

README.md

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<div align="center">
99

1010
[![Status](https://img.shields.io/badge/status-active-success.svg)](https://sykesdev.ca/projects/)
11+
[![Version](https://img.shields.io/badge/version-1.0.2-blue.svg)](https://sykesdev.ca/projects/)
1112
[![CI](https://github.com/SystemFiles/backuply/actions/workflows/ci.yml/badge.svg)](https://github.com/SystemFiles/backuply/actions/workflows/ci.yml)
1213
[![CD](https://github.com/SystemFiles/backuply/actions/workflows/cd.yml/badge.svg)](https://github.com/SystemFiles/backuply/actions/workflows/cd.yml)
1314
[![GitHub Issues](https://img.shields.io/github/issues/systemfiles/backuply.svg)](https://github.com/SystemFiles/backuply/issues)
@@ -24,102 +25,128 @@
2425

2526
## 🧐 About <a name = "about"></a>
2627

27-
Simple backup client written in NodeJS with an emphasis on ease-of-use. Has the ability to create both full backups and then differential backups to save space and time.
28+
Simple backup client written in NodeJS with an emphasis on ease-of-use and speed. Has the ability to create both full backups and then differential backups to save space and time.
2829

2930
## 💾 Installation
3031

3132
Install Backuply using NPM
3233

3334
```bash
35+
# Install the latest version
3436
npm i -g backuply
37+
38+
# Install a specific version
39+
npm i -g backuply@<tag>
3540
```
3641

3742
## 👷‍♂️ Usage
3843

39-
Using backuply is simple by design. Simply start with the operation (backup, restore, or config) and specify any options to apply.
44+
Using Backuply is simple by design. Simply start with the operation (backup, restore, or config) and optionally specify any additional options to apply. Backuply will work without any initial configuration making it easy to pick up and use right away...
4045

4146
```
4247
Usage: backuply <command> [options...]
4348
4449
Commands:
45-
backuply config configure backuply
46-
backuply backup performs a custom backup of a select directory(s)
47-
backuply restore perform a restore from a target backup
50+
app.js config configure backuply
51+
app.js list Displays a list of all backups that are currently known by the
52+
system. Use --name to filter backups by name
53+
app.js backup Performs a custom backup of a select directory(s)
54+
app.js restore Perform a restore from a target backup
4855
4956
Options:
5057
--help Show help [boolean]
5158
--version Show version number [boolean]
5259
```
5360

54-
Most functionality such as backup type is automatically determined based on how you are creating the backup. Backup options shown below
61+
Most functionality such as backup type is automatically determined based on how you are creating the backup. Backup options shown below.
5562

5663
```
57-
backuply backup
64+
Usage: backuply backup [options...]
5865
5966
Descrtiption: Performs a custom backup of a select directory(s)
6067
6168
Positionals:
62-
name the name for this backup [string]
63-
source the source directory to use for the backup. This is the directory that
64-
will be at the root of your backup [string]
65-
dest the destination path which will contain the backup. [string]
69+
name The name for this backup [string]
70+
source The source directory to use for the backup. This is the directory that
71+
will be at the root of your backup [string]
72+
dest The destination path which will contain the backup. [string]
6673
6774
Options:
6875
--help Show help [boolean]
6976
--version Show version number [boolean]
70-
--ref a reference id or name for the full backup used in generating a dif
77+
--ref A reference id or name for the full backup used in generating a dif
7178
ferential backup based on the reference. [string]
79+
7280
Examples:
7381
# Will create a full backup of the source directory
7482
- backuply backup <name> <source_path> <destination_path>
7583
# Will create a differential backup when recognizing referenced full backup
7684
- backuply backup <name> <source_path> <destination_path> --ref <name/uuid>
7785
```
7886

87+
> By using a backup name for the reference parameter will provide **only** the latest full backup that matches the specified name ... If you intend on creating a differential backup with a full backup base other than the most recent full backup for backups matching a certain name, you will likely have to list and select the backup using a reference ID for the target backup. [Tracking Issue](https://github.com/SystemFiles/backuply/issues/38)
88+
7989
Restoring from an existing backup could not be easier.
8090

8191
```
82-
backuply restore
92+
Usage: backuply restore [options...]
8393
8494
Description: Perform a restore from a target backup
8595
8696
Positionals:
87-
ref the full uuid or name for the backup to restore [string]
88-
dest path to destination restore directory [string]
97+
ref The full uuid or name for the backup to restore [string]
98+
dest Path to destination restore directory [string]
99+
100+
Options:
101+
--help Show help [boolean]
102+
--version Show version number [boolean]
103+
--full If using a name reference, tells backuply whether to restore only t
104+
he latest full backup (if exists with ref) [boolean]
105+
```
106+
107+
Listing backups can be a useful way to discover important information related to the backups you have created in the past. It can also be a good way to get reference ID's for old backups that you may want to restore or use as a base for a new backup.
108+
109+
```
110+
Usage: backuply list [search term]
111+
112+
Description: Displays a list of all backups that are currently known by the system.
113+
Use --name to filter backups by name
89114
90115
Options:
91116
--help Show help [boolean]
92117
--version Show version number [boolean]
118+
--name An optional variable used to filter backups by their
119+
user-given names [string]
93120
```
94121

95122
### ⚙️ App Configuration
96123

97124
Making changes to any app configuration can be done in a single one-line command which is capable of modifying multiple attributes at a time. See below for usage details and some examples
98125

99126
```
100-
backuply config
127+
Usage: backuply config [keys...] [values...]
101128
102-
Description: Configure backuply
129+
Description: Configures backuply
103130
104131
Options:
105132
--help Show help [boolean]
106133
--version Show version number [boolean]
107-
--db.path Configure the path to the local database used to store backup
108-
metadata [string]
134+
--db.path Configure the path to the local database used to store
135+
backup metadata [string]
109136
--log.level Configure the logging level [string]
110137
111138
Examples:
112-
# Enable debug logging
139+
# Enable debug logging (default is INFO)
113140
- backuply config --log.level DEBUG
114-
# Change local db path
141+
# Change local db path (default depends on platform ... for example on linux: /home/<you>/.config/backuply/db.json)
115142
- backuply config --db.path ~/Documents/backuply/db.json
116143
```
117144

118145
Future iterations with more config options will follow the same format and will also be documented in the `config --help` subcommand.
119146

120147
## 🧩 Contributing
121148

122-
If you would like to contribute an idea, feature request, or bugfix please start by creating an [issue](https://github.com/SystemFiles/backuply/issues)
149+
If you would like to contribute an idea, feature request, or bugfix please start by creating an [issue](https://github.com/SystemFiles/backuply/issues). I would greatly appreciate any constructive criticism and help from the community!
123150

124151
## 👷‍♂️ Authors <a name = "authors" >
125152

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1010
},
1111
"scripts": {
12-
"start": "NODE_ENV=dev tsc && node dist/app.js restore --ref 7203e58c-2e31-40f1-b6db-fd48cf98ca05 --dest ./dev/restore2",
12+
"start": "NODE_ENV=dev tsc && node dist/app.js backup --name backup-test --source ~/Documents/Personal --dest ./dev --ref backup-test",
1313
"build": "NODE_ENV=production tsc",
1414
"lint": "NODE_ENV=test eslint \"{src,libs,test}/**/*.ts\" --fix",
1515
"test": "echo \"WARN: no test specified\" && exit 0"

src/app.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import { AppConfig } from './lib/configuration.js'
99
import { DatabaseManager } from './lib/database.js'
1010
import { log } from './lib/logger.js'
1111

12-
// Define commandline options
13-
const cmdArgs = parseArgs()
14-
1512
const run = async () => {
1613
sayHello()
1714

15+
// Parse commandline options
16+
const cmdArgs = parseArgs()
17+
1818
// Handle: Perform all app configurations first
1919
if (cmdArgs['_'].toString() === 'config') {
2020
const conf = AppConfig.getInstance()
@@ -52,7 +52,7 @@ const run = async () => {
5252
break
5353
}
5454
case 'restore': {
55-
const [ res, err ] = await restoreBackup(cmdArgs['ref'], cmdArgs['dest'])
55+
const [ res, err ] = await restoreBackup(cmdArgs['ref'], cmdArgs['dest'], cmdArgs['full'])
5656

5757
if (err) {
5858
log(`Something went wrong when attempting to restore a backup. Reason: ${err.message}`)

src/common/commands/backup.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { cwd } from 'process'
44
import { BackupManager } from '../../lib/backup.js'
55
import { DatabaseManager } from '../../lib/database.js'
66
import { log } from '../../lib/logger.js'
7-
import { BackupRecord } from '../types.js'
7+
import { BackupException } from '../exceptions.js'
8+
import { compareRecordsByCreationTime, getLatestBackupByName } from '../functions.js'
9+
import { BackupRecord, BackupType } from '../types.js'
810

911
export async function makeBackup(
1012
name: string,
@@ -30,7 +32,7 @@ export async function makeBackup(
3032
}
3133
export async function listBackups(name?: string): Promise<Error> {
3234
const db: DatabaseManager = DatabaseManager.getInstance()
33-
const [ records, err ] = await db.findAllRecords()
35+
const [ records, err ] = db.findAllRecords()
3436
if (err) return err
3537
let fRecords = records
3638

@@ -71,9 +73,15 @@ export async function differentialBackup(
7173
// Check if refID passed or refName
7274
let refId = ref
7375
if (!/\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b/.test(ref)) {
74-
// Translate ref name to an ID to use
7576
log(`Reference backup was not presented as UUID ... attempting to translate to UUID from presumed name ...`)
76-
refId = ref
77+
78+
// Translate ref name to an ID to use
79+
const [ latestFull, err ] = getLatestBackupByName(ref, BackupType.FULL)
80+
if (err)
81+
throw new BackupException(`Failed to translate backup for backup reference, ${ref} ... Reason: ${err.message}`)
82+
83+
refId = latestFull.id
84+
log(`Translation complete. NAME (${ref}) > UUID (${refId})`)
7785
}
7886

7987
// Perform the backup and return

src/common/commands/parsing.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function parseArgs():
1616
const cmd = yargs(hideBin(process.argv))
1717

1818
// Set options
19-
cmd.command('config', 'configure backuply', (yargs) => {
19+
cmd.command('config', 'Configure Backuply', (yargs) => {
2020
return yargs
2121
.option('db.path', {
2222
type: 'string',
@@ -30,49 +30,54 @@ export function parseArgs():
3030

3131
cmd.command(
3232
'list',
33-
'displays a list of all backups that are currently known by the system. Use --name to filter backups by name',
33+
'Displays a list of all backups that are currently known by the system. Use --name to filter backups by name',
3434
(yargs) => {
3535
return yargs.option('name', {
36-
describe: 'An optional variable used for some info commands to refine info operations',
36+
describe: 'An optional variable used to filter backups by their user-given names',
3737
type: 'string'
3838
})
3939
}
4040
)
4141

4242
// Configure custom backups
43-
cmd.command('backup', 'performs a custom backup of a select directory(s)', (yargs) => {
43+
cmd.command('backup', 'Performs a custom backup of a select directory(s)', (yargs) => {
4444
return yargs
4545
.positional('name', {
46-
describe: 'the name for this backup',
46+
describe: 'The name for this backup',
4747
type: 'string'
4848
})
4949
.positional('source', {
5050
describe:
51-
'the source directory to use for the backup. This is the directory that will be at the root of your backup',
51+
'The source directory to use for the backup. This is the directory that will be at the root of your backup',
5252
type: 'string'
5353
})
5454
.positional('dest', {
55-
describe: 'the destination path which will contain the backup.',
55+
describe: 'The destination path which will contain the backup.',
5656
type: 'string'
5757
})
5858
.option('ref', {
5959
description:
60-
'a reference id or name for the full backup used in generating a differential backup based on the reference.',
60+
'A reference id or name for the full backup used in generating a differential backup based on the reference.',
6161
type: 'string'
6262
})
6363
})
6464

6565
// Restore from backup
66-
cmd.command('restore', 'perform a restore from a target backup', (yargs) => {
66+
cmd.command('restore', 'Perform a restore from a target backup', (yargs) => {
6767
yargs
6868
.positional('ref', {
69-
describe: 'the full uuid or name for the backup to restore',
69+
describe: 'The full uuid or name for the backup to restore',
7070
type: 'string'
7171
})
7272
.positional('dest', {
73-
describe: 'path to destination restore directory',
73+
describe: 'Path to destination restore directory',
7474
type: 'string'
7575
})
76+
.option('full', {
77+
describe:
78+
'If using a name reference, tells backuply whether to restore only the latest full backup (if exists with ref)',
79+
type: 'boolean'
80+
})
7681
})
7782

7883
// Set general parse config

src/common/commands/restore.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import { resolve } from 'path/posix'
33
import { cwd } from 'process'
44
import { log } from '../../lib/logger.js'
55
import { RestoreManager } from '../../lib/restore.js'
6+
import { BackupException } from '../exceptions.js'
7+
import { getLatestBackupByName } from '../functions.js'
8+
import { BackupType } from '../types.js'
69

7-
export async function restoreBackup(ref: string, dest: string): Promise<[string, Error]> {
10+
export async function restoreBackup(ref: string, dest: string, full = false): Promise<[string, Error]> {
811
if (!ref || ref.length === 0 || !dest || dest.length === 0) {
912
return [ null, new Error('Invalid reference ID or destination specified ...') ]
1013
}
@@ -16,7 +19,13 @@ export async function restoreBackup(ref: string, dest: string): Promise<[string,
1619
if (!/\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b/.test(ref)) {
1720
// Translate ref name to an ID to use
1821
log(`Reference backup was not presented as UUID ... attempting to translate to UUID from presumed name ...`)
19-
refId = ref
22+
23+
// Translate ref name to an ID to use
24+
const [ latest, err ] = getLatestBackupByName(ref, full ? BackupType.FULL : BackupType.DIFF)
25+
if (err)
26+
throw new BackupException(`Failed to translate backup for backup reference, ${ref} ... Reason: ${err.message}`)
27+
28+
refId = latest.id
2029
log(`Translation complete. NAME (${ref}) > UUID (${refId})`)
2130
}
2231

src/common/functions.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { pathExists } from 'fs-extra'
44
import { chown, readdir, mkdir } from 'fs/promises'
55
import { userInfo } from 'os'
66
import { join } from 'path/posix'
7+
import { DatabaseManager } from '../lib/database.js'
78
import { log } from '../lib/logger.js'
89
import { PACKAGE_NAME } from './constants.js'
9-
import { BackupRecord, Directory } from './types.js'
10+
import { BackupRecord, BackupType, Directory } from './types.js'
1011

1112
// Pure getAppDataPath
1213
export function getAppDataPath(): string {
@@ -51,6 +52,30 @@ export function compareByDepth(dirA: Directory, dirB: Directory): number {
5152
return 0
5253
}
5354

55+
// Used to sort backup records by name (alphabetical, ascending)
56+
export function compareRecordsByCreationTime(recordA: BackupRecord, recordB: BackupRecord): number {
57+
const dateA = new Date(recordA.created)
58+
const dateB = new Date(recordB.created)
59+
60+
if (dateA < dateB) return 1
61+
if (dateA > dateB) return -1
62+
return 0
63+
}
64+
65+
// Translate name to uuid
66+
export function getLatestBackupByName(name: string, type?: BackupType): [BackupRecord, Error] {
67+
try {
68+
const db = DatabaseManager.getInstance()
69+
const [ res, err ] = db.findRecordsByName(name, type)
70+
if (err || res.length === 0) {
71+
throw new Error(`Failed to get any records for the given name, ${name}`)
72+
}
73+
return [ res.sort(compareRecordsByCreationTime).shift(), null ]
74+
} catch (err) {
75+
return [ null, err ]
76+
}
77+
}
78+
5479
// Create a directory with optional permissions modes
5580
export async function createDirectory(
5681
rootPath: string,

0 commit comments

Comments
 (0)