@ -4,7 +4,7 @@ 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 osproc , os , logging , sequtils , times
import edit_routine_dialog
import " ../models/routine " , " ../shared "
@ -13,14 +13,9 @@ viewable RoutineList:
runStatus : string
selected : seq [ RoutineId ]
# hey, hook.
hooks :
# yes, you, the build hook.
build :
# i love you. :)
state . selected = selectedPreload ( )
proc changed ( state : bool )
state . selected = selectedPreload ( )
var thread : Thread [ RoutineListState ]
@ -32,6 +27,8 @@ proc rsyncThread(list: RoutineListState) {.thread.} =
createDir ( appPath )
var logger = newFileLogger ( appPath & " /errors.log " )
let dt = now ( )
let nowDT = dt . format ( " M-d-YYYY h:mm:ss tt " )
#i'm not sure if using threadvar here is needed, but doing it just in case. :)
var rsyncRunCmd {. threadvar . } : string
@ -50,209 +47,238 @@ proc rsyncThread(list: RoutineListState) {.thread.} =
routineRuncount + = 1
for source in routine . sources :
for destination in routine . destinations :
#try without requiring superuser privs by default.
rsyncRunCmd = " rsync -aq " & source & " " & destination
#try without requiring superuser privs by default.
#quote sources and destinations to handle possible spaces.
rsyncRunCmd = " rsync -aq " & " ' " & source & " ' " & " " & " ' " & destination & " ' "
rsyncRun = execCmdEx ( rsyncRunCmd )
if rsyncRun . exitCode ! = 0 :
#handle permission denied error.
if rsyncRun . exitCode = = 23 :
rsyncRun . exitCode = 0
#rsyncRunCmd = "SUDO_ASKPASS=" & getAskPassPath() & " sudo -A rsync -aq " & source & " " & destination
rsyncRunCmd = " pkexec rsync -aq " & source & " " & destination
rsyncRun . exitCode = 0
#quote sources and destinations to handle possible spaces.
rsyncRunCmd = " pkexec 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 )
rsyncErrors . add ( " ( " & nowDT & " ) " & " 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 )
rsyncErrors . add ( " ( " & nowDT & " ) " & " 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
#just using file names, mod times, and size (same as bkup run itself).
#quote sources and destinations to handle possible spaces.
rsyncCheckCmd = " rsync -rn " & " ' " & source & " ' " & " " & " ' " & destination & " ' "
rsyncCheckRun = execCmdEx ( rsyncCheckCmd )
if rsyncCheckRun . exitCode ! = 0 :
#handle permission denied error.
if rsyncCheckRun . exitCode = = 23 :
rsyncCheckRun . exitCode = 0
#rsyncCheckCmd = "SUDO_ASKPASS=" & getAskPassPath() & " sudo -A rsync -rn " & source & " " & destination
rsyncCheckCmd = " pkexec rsync -rn " & source & " " & destination
rsyncCheckRun . exitCode = 0
#quote sources and destinations to handle possible spaces.
rsyncCheckCmd = " pkexec 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 )
rsyncErrors . add ( " ( " & nowDT & " ) " & " 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 )
rsyncErrors . add ( " ( " & nowDT & " ) " & " 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> "
list . runStatus = " <span color= \" #FFA651 \" size= \" large \" >No Bkup Routines were enabled. None were run.</span> "
else :
list . runStatus = " <span color= \" #6fffa3 \" size= \" large \" >Bkup Complete!</span> "
list . redrawFromThread ( )
method view ( list : RoutineListState ) : Widget =
result = gui :
ScrolledWindow :
result = gui :
Box :
orient = OrientY
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 :
orient = OrientY
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
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 :
spacing = 6
Box :
orient = OrientX
margin = 6
spacing = 6
Switch {. expand : false , vAlign : AlignCenter . } :
state = sequtils . any ( list . selected , proc ( id : RoutineId ) : bool = id = = routine . id )
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 )
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 )
list . selected = selectedPreload ( )
# 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 )
list . selected = selectedPreload ( )
if routine . id in list . selected :
Box :
orient = OrientX
spacing = 6
orient = OrientY
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> "
text = " <span color= \" #6fffa3 \" >Sources:</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
#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
else :
Box {. expand : false . } :
orient = OrientY
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 :
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
if list . routineModel . routineSeq ( ) . len ( ) > 0 :
Box {. expand : false . } :
orient = OrientY
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
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 )
#[if not list.routineModel.routineSeq().len() > 0:
Box :
orient = OrientX
spacing = 6
margin = 6
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