A desktop backup program focusing on ease-of-use and simplicity, as well as quality, low resource usage, and performance.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
EZ-Bkup/models/routine.nim

196 lines
6.0 KiB

#[Copyright 2023 ITwrx.
This file is part of EZ-Bkup.
EZ-Bkup is released under the General Public License 3.0.
See COPYING or <https://www.gnu.org/licenses/> for details.]#
import std/[tables, strutils, sugar, hashes, algorithm, os]
from std/sequtils import toSeq
import tiny_sqlite
import "../shared"
# We create a custom type for representing the ID of a routine.
# This way Nim's type system automatically prevents us from using it as a regular integer and
# from confusing it with the ID of another entity.
type RoutineId* = distinct int64
proc `==`*(a, b: RoutineId): bool {.borrow.}
proc hash*(id: RoutineId): Hash {.borrow.}
proc `$`*(id: RoutineId): string =
result = "routine" & $int64(id)
type Routine* = object
id*: RoutineId
name*: string
selected*: bool
#selected by default.
selByDef*: bool
sources*: seq[string]
destinations*: seq[string]
proc stringToSeq(commaSepString: string):seq[string] =
var newSeq: seq[string]
if hasCommas(commaSepString) == true:
newSeq = toSeq(commaSepString.split(','))
else:
newSeq = @[commaSepString]
return newSeq
proc seqToString(strSeq: seq[string]):string =
var newStr: string
if strSeq.len >= 1:
newStr = strSeq.join(",")
else:
newStr = ""
return newStr
proc locationsExist*(routine: Routine): bool =
var logMsg: string
var exists: seq[int]
for source in routine.sources:
if dirExists(source) or fileExists(source):
exists.add(1)
else:
logMsg = "(" & nowDT & ")" & " Source not found: " & source
writeErrorToLog(logMsg)
exists.add(0)
for dest in routine.destinations:
if dirExists(dest) or fileExists(dest):
exists.add(1)
else:
logMsg = "(" & nowDT & ")" & " Destination not found: " & dest
writeErrorToLog(logMsg)
exists.add(0)
if 0 in exists:
return false
else:
return true
type RoutineModel* = ref object
## Model for storing all routines. We model this as a ref object, so that changes
## made by any widget are also known to all other widgets that use the model.
db: DbConn
routines*: Table[RoutineId, Routine]
proc cmpRoutines(a, b: Routine): int =
cmp(a.name, b.name)
#returns a seq so we can iterate in routine_list.
proc routineSeq*(model: RoutineModel): seq[Routine] =
var routineSeq: seq[Routine]
## Returns a seq of all routines. "id" is necessary to loop through table.
for id, routine in model.routines:
routineSeq.add(routine)
routineSeq.sort(cmpRoutines, Ascending)
return routineSeq
let homePath = expandTilde("~")
proc newRoutineModel*(path: string = homePath & "/.ez-bkup/ez-bkup.sqlite"): RoutineModel =
## Load a RoutineModel from a database
var db: DbConn
try:
db = openDatabase(path)
except:
writeErrorToLog("Unable to open and/or create sqlite3 database. Please make sure sqlite3 is installed.")
db = openDatabase("")
# Create the Routine table
db.exec("""
CREATE TABLE IF NOT EXISTS Routine(
id INTEGER PRIMARY KEY,
name TEXT,
selByDef INTEGER,
sources TEXT,
destinations TEXT
)
""")
# Load all existing routines into RoutineModel.routines
var routines = initTable[RoutineId, Routine]()
for row in db.iterate("SELECT id, name, selByDef, sources, destinations FROM Routine"):
let (id, name, selByDef, sources, destinations) = row.unpack((RoutineId, string, bool, string, string))
var sourcesSeq: seq[string]
#no sources exist in DB.
if sources == "":
sourcesSeq = @[]
else:
#convert "sources" comma-separated string to seq[string].
sourcesSeq = stringToSeq(sources)
var destinationsSeq: seq[string]
#no destinations exist in DB.
if destinations == "":
destinationsSeq = @[]
else:
#convert "destinations" comma-separated string to seq[string].
destinationsSeq = stringToSeq(destinations)
routines[id] = Routine(id: id, name: name, selByDef: selByDef, sources: sourcesSeq, destinations: destinationsSeq)
result = RoutineModel(db: db, routines: routines)
proc add*(model: RoutineModel, routine: Routine) =
## Adds a new routine to the model
#convert routine.sources seq[string] to a comma-separated string to store as TEXT in sqlite.
let stringSources = seqToString(routine.sources)
#convert routine.destinations seq[string] to a comma-separated string to store as TEXT in sqlite.
let stringDestinations = seqToString(routine.destinations)
# Insert new routine into database
model.db.exec(
"INSERT INTO Routine (name, selByDef, sources, destinations) VALUES (?, ?, ?, ?)",
routine.name, routine.selByDef, stringSources, stringDestinations
)
# Insert new routine into RoutineModel.routines
let id = RoutineId(model.db.lastInsertRowId)
model.routines[id] = routine.dup(id = id)
proc update*(model: RoutineModel, routine: Routine) =
## Updates an existing routine. Routines are compared by their ID.
#convert routine.sources seq[string] to a comma-separated string to store as TEXT in sqlite.
let stringSources = seqToString(routine.sources)
#convert routine.destinations seq[string] to a comma-separated string to store as TEXT in sqlite.
let stringDestinations = seqToString(routine.destinations)
# Update routine in database
model.db.exec(
"UPDATE Routine SET name = ?, selByDef = ?, sources = ?, destinations = ? WHERE id = ?",
routine.name, routine.selByDef, stringSources, stringDestinations, routine.id
)
# Update RoutineModel.routines
model.routines[routine.id] = routine
proc delete*(model: RoutineModel, id: RoutineId) =
## Deletes the routine with the given ID
# Delete routine from database
model.db.exec("DELETE FROM Routine WHERE id = ?", id)
# Update RoutineModel.routines
model.routines.del(id)
#preload the selected seq with routines that have selByDef == true.
proc selectedPreload*(): seq[RoutineId] =
var selected: seq[RoutineId]
let model = newRoutineModel(databasePath)
for routine in model.routineSeq():
if routine.selByDef == true:
selected.add(routine.id)
return selected