#[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 owlkettle
import std / osproc , std / os , std / logging
import edit_routine_dialog
import " ../models/routine " , " ../shared "
viewable RoutineList :
routineModel : RoutineModel
runStatus : string
selected : seq [ RoutineId ]
# hey, hook.
hooks :
# yes, you, the build hook.
build :
# i love you. :)
state . selected = selectedPreload ( )
proc changed ( state : bool )
var thread : Thread [ RoutineListState ]
proc rsyncThread ( list : RoutineListState ) {. thread . } =
let appPath = getHomeDir ( ) & " .ez-bkup "
if not dirExists ( appPath ) :
createDir ( appPath )
var logger = newFileLogger ( appPath & " /errors.log " )
#i'm not sure if using threadvar here is needed, but doing it just in case. :)
var rsyncRunCmd {. threadvar . } : string
var rsyncRun {. threadvar . } : tuple [ output : string , exitCode : int ]
var rsyncCheckCmd {. threadvar . } : string
var rsyncCheckRun {. threadvar . } : tuple [ output : string , exitCode : int ]
var rsyncErrors : seq [ string ]
var routineRunCount : int
routineRunCount = 0
for routine in list . routineModel . routineSeq ( ) :
if routine . id in list . selected :
#skip routines that don't have at least one source and one destination.
if routine . sources . len ! = 0 and routine . destinations . len ! = 0 :
list . runStatus = " <span color= \" #FFE97B \" size= \" large \" >Horses, please to be holding...</span> "
routineRuncount + = 1
for source in routine . sources :
for destination in routine . destinations :
#try without requiring superuser privs by default.
rsyncRunCmd = " rsync -aq " & source & " " & destination
rsyncRun = execCmdEx ( rsyncRunCmd )
if rsyncRun . exitCode ! = 0 :
#handle permission denied error.
if rsyncRun . exitCode = = 23 :
rsyncRun . exitCode = 0
if getAskPassPath ( ) = = " " :
let err = " No ssh-askpass binary found. Please install an ssh-askpass package for your distro, and let us know if EZ-Bkup still can ' t detect it ' s location. "
rsyncErrors . add ( err )
else :
rsyncRunCmd = " SUDO_ASKPASS= " & getAskPassPath ( ) & " sudo -A rsync -aq " & source & " " & destination
rsyncRun = execCmdEx ( rsyncRunCmd )
if rsyncRun . exitCode ! = 0 :
rsyncErrors . add ( " EZ-Bkup ' s rsync process(es) returned error ( " & $ rsyncRun . output & " ) while attempting to back up " & source & " to " & destination )
#handle non-perms related error.
else :
rsyncErrors . add ( " EZ-Bkup ' s rsync process(es) returned error ( " & $ rsyncRun . output & " ) while attempting to back up " & source & " to " & destination )
#explicitly check that sources were copied to destinations.
#just using file names, mod times, and size (same as bkup run itself).
rsyncCheckCmd = " rsync -rn " & source & " " & destination
rsyncCheckRun = execCmdEx ( rsyncCheckCmd )
if rsyncCheckRun . exitCode ! = 0 :
#handle permission denied error.
if rsyncCheckRun . exitCode = = 23 :
rsyncCheckRun . exitCode = 0
if getAskPassPath ( ) = = " " :
let err = " No ssh-askpass binary found. Please install an ssh-askpass package for your distro, and let us know if EZ-Bkup still can ' t detect it ' s location. "
rsyncErrors . add ( err )
else :
rsyncCheckCmd = " SUDO_ASKPASS= " & getAskPassPath ( ) & " sudo -A rsync -rn " & source & " " & destination
rsyncCheckRun = execCmdEx ( rsyncCheckCmd )
if rsyncCheckRun . exitCode ! = 0 :
rsyncErrors . add ( " EZ-Bkup ' s rsync process(es) returned error ( " & $ rsyncRun . output & " ) while attempting to verify that " & source & " got backed up to " & destination )
#handle non-perms related error.
else :
rsyncErrors . add ( " EZ-Bkup ' s rsync process(es) returned error ( " & $ rsyncRun . output & " ) while attempting to verify that " & source & " got backed up to " & destination )
if rsyncErrors . len > 0 :
list . runStatus = " <span color= \" #ff6b6b \" size= \" large \" >Error! Please see ~/.ez-bkup/errors.log</span> "
for err in rsyncErrors :
logger . log ( lvlError , err )
elif routineRunCount = = 0 :
list . runStatus = " <span color= \" #FFA651 \" size= \" large \" >Meh. No Bkup Routines were run.</span> "
else :
list . runStatus = " <span color= \" #6fffa3 \" size= \" large \" >Bkup Complete!</span> "
list . redrawFromThread ( )
method view ( list : RoutineListState ) : Widget =
result = gui :
ScrolledWindow :
Box :
orient = OrientY
if list . routineModel . routineSeq ( ) . len ( ) > 0 :
ListBox :
for it , routine in list . routineModel . routineSeq ( ) :
Box :
orient = OrientY
margin = 6
spacing = 6
Box :
orient = OrientX
margin = 6
spacing = 6
Switch {. expand : false , vAlign : AlignCenter . } :
state = routine . selByDef
tooltip = " Enable/Disable this Routine for the current Bkup run. "
proc changed ( state : bool ) =
if state = = true :
list . selected . add ( routine . id )
else :
let index = list . selected . find ( routine . id )
if index ! = - 1 :
list . selected . delete ( index )
if not list . changed . isNil :
list . changed . callback ( true )
Label :
text = " <span size= \" large \" > " & routine . name & " </span> "
xAlign = 0
useMarkup = true
# Edit Button
Button {. expand : false . } :
#icon = "entity-edit"
icon = " entity-edit-dark-theme "
tooltip = " Edit this Routine. "
proc clicked ( ) =
## Opens the EditRoutineDialog for updating the existing routine
let ( res , state ) = list . app . open : gui :
EditRoutineDialog :
routine = routine
mode = EditRoutineUpdate
if res . kind = = DialogAccept :
# The "Update" button was clicked
list . routineModel . update ( EditRoutineDialogState ( state ) . routine )
# Delete Button
Button {. expand : false . } :
icon = " user-trash-symbolic "
tooltip = " Delete this Routine. Warning: will not ask you to confirm. "
proc clicked ( ) =
list . routineModel . delete ( routine . id )
if routine . id in list . selected :
Box :
orient = OrientY
margin = 6
spacing = 6
Label :
text = " <span color= \" #6fffa3 \" >Sources:</span> "
xAlign = 0
useMarkup = true
#routineSeq returns an empty string when no sources exist.
if routine . sources . len ! = 0 :
for it , routineSource in routine . sources :
Box :
orient = OrientX
spacing = 6
margin = 6
Label :
text = routineSource
xAlign = 0
else :
Box :
orient = OrientX
spacing = 6
margin = 6
Label :
text = " <span color= \" #ff6b6b \" >Routine will be ignored. Source required.</span> "
xAlign = 0
useMarkup = true
Label :
text = " <span color= \" #6fffa3 \" >Destinations:</span> "
xAlign = 0
useMarkup = true
#routineSeq returns an empty string when no destinations exist.
if routine . destinations . len ! = 0 :
for it , routineDestination in routine . destinations :
Box :
orient = OrientX
spacing = 6
margin = 6
Label :
text = routineDestination
xAlign = 0
else :
Box :
orient = OrientX
spacing = 6
margin = 6
Label :
text = " <span color= \" #ff6b6b \" >Routine will be ignored. Destination required.</span> "
xAlign = 0
useMarkup = true
Box {. expand : false . } :
orient = OrientX
spacing = 6
margin = 6
Label {. expand : false . } :
text = " <span size= \" large \" >Status: </span> "
xAlign = 0
useMarkup = true
Label :
if list . runStatus ! = " " :
text = list . runStatus
else :
text = " <span color= \" #6AC9FF \" size= \" large \" >Waiting patiently...</span> "
xAlign = 0
margin = 6
useMarkup = true
# Run Button
Button {. expand : false . } :
Box :
orient = OrientX
spacing = 6
margin = 6
Icon :
name = " media-floppy "
pixelSize = 40
margin = 4
Label {. expand : false . } :
text = " <span size= \" large \" >Run Bkup!</span> "
xAlign = 0
useMarkup = true
proc clicked ( ) =
#check that every source and destination exists for each routine or don't run bkup and show err status.
#locations not found will be logged. see routine.nim
var allLocationsExist : seq [ bool ]
for routine in list . routineModel . routineSeq ( ) :
if routine . id in list . selected :
if locationsExist ( routine ) = = true :
allLocationsExist . add ( true )
else :
allLocationsExist . add ( false )
if false in allLocationsExist :
#show error status.
list . runStatus = " <span color= \" #ff6b6b \" size= \" large \" >Missing Source(s)/Destination(s) in selected Routine(s).</span> "
else :
createThread ( thread , rsyncThread , list )
else :
Box {. expand : false . } :
orient = OrientY
margin = 6
spacing = 12
Label :
text = " <span size= \" large \" >You don ' t have any Bkup Routines!!! 🙂</span> "
xAlign = 0
useMarkup = true
Label :
text = " <span size= \" large \" >Please click on the + Routine button above to create your first Routine.</span> "
xAlign = 0
useMarkup = true
export RoutineList