Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
proofrock committed Oct 16, 2021
2 parents 4ecf58f + 5c84f77 commit e07ed31
Show file tree
Hide file tree
Showing 12 changed files with 307 additions and 69 deletions.
46 changes: 38 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 🗃️ snapkup v0.1.0
# 🗃️ snapkup v0.2.0

Snapkup is a simple backup tool that takes snapshots of your filesystem (or the parts that you'll decide), storing them efficiently and conveniently.

Expand All @@ -10,7 +10,7 @@ Snapkup's goal is to store efficiently one or more filesystem's situation at giv
- You register one or more backup roots, directory or files that will be snapshotted
- You take one or more snapshots. Snapkup lists all the tree for those roots, taking a snapshot of the contents
- All the files in the roots are deduplicated, and only the files that are different are stored
- All files that can be compressed are stored as such, using `zstd -9`
- It's possible to compress the files, using `zstd -9`
- Files are stored in an efficient manner, with a shallow directory structure.
- You can restore the situation of the roots at a given snapshot, later on
- Files' and dirs' mode and modification time are preserved
Expand All @@ -29,23 +29,53 @@ We will backup the contents of the `C:\MyImportantDir`, using the `C:\MySnapkupD

### Register the directory to backup as a root

`snapkup.exe -d C:\MySnapkupDir add-root C:\MyImportantDir`
`snapkup.exe -d C:\MySnapkupDir root add C:\MyImportantDir`

### Take your first snapshot

`snapkup.exe -d C:\MySnapkupDir snap`
`snapkup.exe -d C:\MySnapkupDir snap take`

*add `-z` if you want to compress the files being backed up*.
*Add `-z` if you want to compress the files being backed up. Add `-l` to specify a label.*

`snapkup.exe -d C:\MySnapkupDir snap take -z -l "My first label"`

*Alias: `snap do`*

### Change the label of a snap

`snapkup.exe -d C:\MySnapkupDir snap label 0 "My First Label"`

*Alias: `snap lbl`*

### Get info on a snapshot

`snapkup.exe -d C:\MySnapkupDir snap info 0`

*gives info like: number of files, number of dirs, size, and how much space on backup filesystem will be freed if this snap is deleted.*

### Get the file list on a snapshot

`snapkup.exe -d C:\MySnapkupDir snap filelist 0`

*prints a list of the directories and files for a snap.*

*Alias: `snap fl`*

### Delete it, because... just because.

`snapkup.exe -d C:\MySnapkupDir del-snap 0`
`snapkup.exe -d C:\MySnapkupDir snap del 0`

*Alias: `snap rm`*

### Or restore it!

`snapkup.exe -d C:\MySnapkupDir restore 0 C:\MyRestoreDir`
`snapkup.exe -d C:\MySnapkupDir snap restore 0 C:\MyRestoreDir`

*the destination directory must be empty. It is also possible to specify a prefix path to select only a part of the file list:*

`snapkup.exe -d C:\MySnapkupDir snap restore 0 C:\MyRestoreDir --prefix-path /foo/bar`

*the destination directory should be empty.*
*Alias: `snap res`*

## Status

Expand Down
1 change: 0 additions & 1 deletion src/commands/add_root/add_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ func AddRoot(bkpDir string, toAdd string) error {
return errBeginning
}

// TODO QueryOnce
throwaway := 1
row := db.QueryRow("SELECT 1 FROM ROOTS WHERE PATH = ?", toAdd)
if errQuerying := row.Scan(&throwaway); errQuerying == nil {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/del_snap/del_snap.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func DelSnap(bkpDir string, toDel int) error {
if errScanning := rows.Scan(&hash); errScanning != nil {
return errScanning
}
pathToDel := path.Join(bkpDir, hash[0:2], hash[2:])
pathToDel := path.Join(bkpDir, hash[0:1], hash)
if errDeleting := os.Remove(pathToDel); errDeleting != nil {
fmt.Fprintf(os.Stderr, "ERROR: deleting file %s; %v\n", hash, errDeleting)
}
Expand Down
92 changes: 92 additions & 0 deletions src/commands/info_snap/info_snap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package info_snap

import (
"database/sql"
"fmt"

"github.com/proofrock/snapkup/util"
)

const sql1 = `WITH
consts AS (SELECT ? AS snap),
data AS (
SELECT 1 AS key, COUNT(1) AS val FROM ITEMS WHERE SNAP = (SELECT snap FROM consts) AND IS_DIR = 0
UNION ALL SELECT 2 AS key, COUNT(1) AS val FROM ITEMS WHERE SNAP = (SELECT snap FROM consts) AND IS_DIR = 1
UNION ALL SELECT 3 AS key, SUM(b.SIZE) AS val FROM ITEMS i, BLOBS b WHERE i.HASH = b.HASH AND SNAP = (SELECT snap FROM consts) AND IS_DIR = 0
UNION ALL SELECT 4 AS key, SUM(BLOB_SIZE) AS val FROM BLOBS WHERE HASH IN (SELECT HASH FROM ITEMS WHERE SNAP = (SELECT snap FROM consts) AND IS_DIR = 0)
UNION ALL SELECT 5 AS key, SUM(BLOB_SIZE) AS val FROM BLOBS
UNION ALL SELECT 6 AS key, SUM(BLOB_SIZE) AS val FROM BLOBS WHERE HASH NOT IN (SELECT HASH FROM ITEMS WHERE SNAP != (SELECT snap FROM consts) AND IS_DIR = 0)
)
SELECT val FROM data ORDER BY key ASC`

var titles = [6]string{
"Files",
"Directories",
"Size",
"Stored size",
"Tot. stored (all snaps)",
"Free when deleted",
}

var isInByte = [6]bool{false, false, true, true, true, true}

const suffixes = "KMGTPE"

const unit = 1024

func fmtBytes(b int64) string {
if b < unit {
return fmt.Sprintf("%d b", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cb",
float64(b)/float64(div), suffixes[exp])
}

func InfoSnap(bkpDir string, snap int) error {
maxLen := 0
for _, title := range titles {
if len(title) > maxLen {
maxLen = len(title)
}
}

dbPath, errComposingDbPath := util.DbFile(bkpDir)
if errComposingDbPath != nil {
return errComposingDbPath
}

db, errOpeningDb := sql.Open("sqlite3", dbPath)
if errOpeningDb != nil {
return errOpeningDb
}
defer db.Close()

rows, errQuerying := db.Query(sql1, snap)
if errQuerying != nil {
return errQuerying
}
defer rows.Close()
i := 0
for rows.Next() {
var val int64
if errScanning := rows.Scan(&val); errScanning != nil {
return errScanning
}
if isInByte[i] {
fmt.Printf(fmt.Sprintf("%%-%ds: %%s\n", maxLen), titles[i], fmtBytes(val))
} else {
fmt.Printf(fmt.Sprintf("%%-%ds: %%d\n", maxLen), titles[i], val)
}
i++
}
if errClosingQry := rows.Err(); errClosingQry != nil {
return errClosingQry
}

return nil
}
30 changes: 23 additions & 7 deletions src/commands/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ import (
"database/sql"
"fmt"
"io/fs"
"math/rand"
"os"
"path"
"time"

_ "github.com/mattn/go-sqlite3"

"github.com/proofrock/snapkup/util"
)

var sqls = [4]string{
var sqls = [5]string{
`CREATE TABLE "PARAMS" (
"KEY" TEXT NOT NULL,
"VALUE" TEXT NOT NULL,
PRIMARY KEY("KEY")
)`,
`CREATE TABLE "SNAPS" (
"ID" INTEGER NOT NULL,
"TIMESTAMP" INTEGER NOT NULL,
"LABEL" TEXT NOT NULL,
PRIMARY KEY("ID")
)`,
`CREATE TABLE "ITEMS" (
Expand All @@ -41,8 +49,6 @@ var sqls = [4]string{
)`,
}

const hex = "0123456789abcdef"

func Init(bkpDir string) error {
if isEmpty, errCheckingEmpty := util.IsEmpty(bkpDir); errCheckingEmpty != nil {
return errCheckingEmpty
Expand Down Expand Up @@ -70,14 +76,24 @@ func Init(bkpDir string) error {
}
}

iv := make([]byte, 16, 16)
rand.Seed(time.Now().Unix())
if _, errRandomizing := rand.Read(iv); errRandomizing != nil {
return errRandomizing
}

if _, errExecing := tx.Exec("INSERT INTO PARAMS (KEY, VALUE) VALUES ('IV', ?)", iv); errExecing != nil {
tx.Rollback()
return errExecing
}

if errCommitting := tx.Commit(); errCommitting != nil {
return errCommitting
}

for i := 0; i < 16; i++ {
for j := 0; j < 16; j++ {
os.Mkdir(path.Join(bkpDir, hex[i:i+1]+hex[j:j+1]), fs.FileMode(0700))
}
hex := []rune("0123456789abcdef")
for i := 0; i < len(hex); i++ {
os.Mkdir(path.Join(bkpDir, string(hex[i])), fs.FileMode(0700))
}

println("Backup directory correctly initialized in ", bkpDir)
Expand Down
28 changes: 28 additions & 0 deletions src/commands/label_snap/label_snap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package labelsnap

import (
"database/sql"

"github.com/proofrock/snapkup/util"
)

func LabelSnap(bkpDir string, snap int, label string) error {
dbPath, errComposingDbPath := util.DbFile(bkpDir)
if errComposingDbPath != nil {
return errComposingDbPath
}

db, errOpeningDb := sql.Open("sqlite3", dbPath)
if errOpeningDb != nil {
return errOpeningDb
}
defer db.Close()

if _, errExecing := db.Exec("UPDATE SNAPS SET LABEL = ? WHERE ID = ?", label, snap); errExecing != nil {
return errExecing
}

println("Ok.")

return nil
}
40 changes: 40 additions & 0 deletions src/commands/list_snap/list_snap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package list_snap

import (
"database/sql"

"github.com/proofrock/snapkup/util"
)

const sql1 = "SELECT PATH FROM ITEMS WHERE SNAP = ? ORDER BY PATH ASC"

func ListSnap(bkpDir string, snap int) error {
dbPath, errComposingDbPath := util.DbFile(bkpDir)
if errComposingDbPath != nil {
return errComposingDbPath
}

db, errOpeningDb := sql.Open("sqlite3", dbPath)
if errOpeningDb != nil {
return errOpeningDb
}
defer db.Close()

rows, errQuerying := db.Query(sql1, snap)
if errQuerying != nil {
return errQuerying
}
defer rows.Close()
for rows.Next() {
var path string
if errScanning := rows.Scan(&path); errScanning != nil {
return errScanning
}
println(path)
}
if errClosingQry := rows.Err(); errClosingQry != nil {
return errClosingQry
}

return nil
}
7 changes: 4 additions & 3 deletions src/commands/list_snaps/list_snaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type snap struct {
id int
timestamp int64
label string
}

func ListSnaps(bkpDir string) error {
Expand All @@ -26,14 +27,14 @@ func ListSnaps(bkpDir string) error {
defer db.Close()

var snaps []snap
rows, errQuerying := db.Query("SELECT ID, TIMESTAMP FROM SNAPS ORDER BY ID DESC")
rows, errQuerying := db.Query("SELECT ID, TIMESTAMP, LABEL FROM SNAPS ORDER BY ID DESC")
if errQuerying != nil {
return errQuerying
}
defer rows.Close()
for rows.Next() {
var snap snap
if errScanning := rows.Scan(&snap.id, &snap.timestamp); errScanning != nil {
if errScanning := rows.Scan(&snap.id, &snap.timestamp, &snap.label); errScanning != nil {
return errScanning
}
snaps = append(snaps, snap)
Expand All @@ -44,7 +45,7 @@ func ListSnaps(bkpDir string) error {

for _, snap := range snaps {
ts := time.UnixMilli(snap.timestamp).Local().Format("2 Jan 2006, 15:04:05 (MST)")
fmt.Printf("Snap %d:\t%s\n", snap.id, ts)
fmt.Printf("Snap %d:\t%s\t%s\n", snap.id, ts, snap.label)
}

return nil
Expand Down
Loading

0 comments on commit e07ed31

Please sign in to comment.