@ -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 . ] #
See COPYING or < https : / / www . gnu . org / licenses / > for details . ] #
import owlkettle
import owlkettle
import std / osproc , std / os , std / logging
import osproc , os , logging , sequtils , times
import edit_routine_dialog
import edit_routine_dialog
import " ../models/routine " , " ../shared "
import " ../models/routine " , " ../shared "
@ -13,15 +13,10 @@ viewable RoutineList:
runStatus : string
runStatus : string
selected : seq [ RoutineId ]
selected : seq [ RoutineId ]
# hey, hook.
hooks :
hooks :
# yes, you, the build hook.
build :
build :
# i love you. :)
state . selected = selectedPreload ( )
state . selected = selectedPreload ( )
proc changed ( state : bool )
var thread : Thread [ RoutineListState ]
var thread : Thread [ RoutineListState ]
proc rsyncThread ( list : RoutineListState ) {. thread . } =
proc rsyncThread ( list : RoutineListState ) {. thread . } =
@ -32,6 +27,8 @@ proc rsyncThread(list: RoutineListState) {.thread.} =
createDir ( appPath )
createDir ( appPath )
var logger = newFileLogger ( appPath & " /errors.log " )
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. :)
#i'm not sure if using threadvar here is needed, but doing it just in case. :)
var rsyncRunCmd {. threadvar . } : string
var rsyncRunCmd {. threadvar . } : string
@ -51,43 +48,45 @@ proc rsyncThread(list: RoutineListState) {.thread.} =
for source in routine . sources :
for source in routine . sources :
for destination in routine . destinations :
for destination in routine . destinations :
#try without requiring superuser privs by default.
#try without requiring superuser privs by default.
rsyncRunCmd = " rsync -aq " & source & " " & destination
#quote sources and destinations to handle possible spaces.
rsyncRunCmd = " rsync -aq " & " ' " & source & " ' " & " " & " ' " & destination & " ' "
rsyncRun = execCmdEx ( rsyncRunCmd )
rsyncRun = execCmdEx ( rsyncRunCmd )
if rsyncRun . exitCode ! = 0 :
if rsyncRun . exitCode ! = 0 :
#handle permission denied error.
#handle permission denied error.
if rsyncRun . exitCode = = 23 :
if rsyncRun . exitCode = = 23 :
rsyncRun . exitCode = 0
rsyncRun . exitCode = 0
#rsyncRunCmd = "SUDO_ASKPASS=" & getAskPassPath() & " sudo -A rsync -aq " & source & " " & destination
#quote sources and destinations to handle possible spaces.
rsyncRunCmd = " pkexec rsync -aq " & source & " " & destination
rsyncRunCmd = " pkexec rsync -aq " & " ' " & source & " ' " & " " & " ' " & destination & " ' "
rsyncRun = execCmdEx ( rsyncRunCmd )
rsyncRun = execCmdEx ( rsyncRunCmd )
if rsyncRun . exitCode ! = 0 :
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.
#handle non-perms related error.
else :
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.
#explicitly check that sources were copied to destinations.
#just using file names, mod times, and size (same as bkup run itself).
#just using file names, mod times, and size (same as bkup run itself).
rsyncCheckCmd = " rsync -rn " & source & " " & destination
#quote sources and destinations to handle possible spaces.
rsyncCheckCmd = " rsync -rn " & " ' " & source & " ' " & " " & " ' " & destination & " ' "
rsyncCheckRun = execCmdEx ( rsyncCheckCmd )
rsyncCheckRun = execCmdEx ( rsyncCheckCmd )
if rsyncCheckRun . exitCode ! = 0 :
if rsyncCheckRun . exitCode ! = 0 :
#handle permission denied error.
#handle permission denied error.
if rsyncCheckRun . exitCode = = 23 :
if rsyncCheckRun . exitCode = = 23 :
rsyncCheckRun . exitCode = 0
rsyncCheckRun . exitCode = 0
#rsyncCheckCmd = "SUDO_ASKPASS=" & getAskPassPath() & " sudo -A rsync -rn " & source & " " & destination
#quote sources and destinations to handle possible spaces.
rsyncCheckCmd = " pkexec rsync -rn " & source & " " & destination
rsyncCheckCmd = " pkexec rsync -rn " & " ' " & source & " ' " & " " & " ' " & destination & " ' "
rsyncCheckRun = execCmdEx ( rsyncCheckCmd )
rsyncCheckRun = execCmdEx ( rsyncCheckCmd )
if rsyncCheckRun . exitCode ! = 0 :
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.
#handle non-perms related error.
else :
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 :
if rsyncErrors . len > 0 :
list . runStatus = " <span color= \" #ff6b6b \" size= \" large \" >Error! Please see ~/.ez-bkup/errors.log</span> "
list . runStatus = " <span color= \" #ff6b6b \" size= \" large \" >Error! Please see ~/.ez-bkup/errors.log</span> "
for err in rsyncErrors :
for err in rsyncErrors :
logger . log ( lvlError , err )
logger . log ( lvlError , err )
elif routineRunCount = = 0 :
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 :
else :
list . runStatus = " <span color= \" #6fffa3 \" size= \" large \" >Bkup Complete!</span> "
list . runStatus = " <span color= \" #6fffa3 \" size= \" large \" >Bkup Complete!</span> "
@ -95,164 +94,191 @@ proc rsyncThread(list: RoutineListState) {.thread.} =
method view ( list : RoutineListState ) : Widget =
method view ( list : RoutineListState ) : Widget =
result = gui :
result = gui :
ScrolledWindow :
Box :
orient = OrientY
Box :
Box :
orient = OrientY
orient = OrientY
if list . routineModel . routineSeq ( ) . len ( ) > 0 :
ScrolledWindow :
ListBox :
Box :
for it , routine in list . routineModel . routineSeq ( ) :
orient = OrientY
Box :
if list . routineModel . routineSeq ( ) . len ( ) > 0 :
orient = OrientY
ListBox :
margin = 6
for it , routine in list . routineModel . routineSeq ( ) :
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 :
Box :
orient = OrientY
orient = OrientY
margin = 6
margin = 6
spacing = 6
spacing = 6
Label :
Box :
text = " <span color= \" #6fffa3 \" >Sources:</span> "
orient = OrientX
xAlign = 0
margin = 6
useMarkup = true
spacing = 6
#routineSeq returns an empty string when no sources exist.
Switch {. expand : false , vAlign : AlignCenter . } :
if routine . sources . len ! = 0 :
state = sequtils . any ( list . selected , proc ( id : RoutineId ) : bool = id = = routine . id )
for it , routineSource in routine . sources :
tooltip = " Enable/Disable this Routine for the current Bkup run. "
Box :
proc changed ( state : bool ) =
orient = OrientX
if state = = true :
spacing = 6
list . selected . add ( routine . id )
margin = 6
else :
Label :
let index = list . selected . find ( routine . id )
text = routineSource
if index ! = - 1 :
xAlign = 0
list . selected . delete ( index )
else :
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 :
Box :
orient = OrientX
orient = OrientY
spacing = 6
margin = 6
margin = 6
spacing = 6
Label :
Label :
text = " <span color= \" #ff6b6b \" >Routine will be ignored. Source required.</span> "
text = " <span color= \" #6fffa3 \" >Sources:</span> "
xAlign = 0
xAlign = 0
useMarkup = true
useMarkup = true
Label :
#routineSeq returns an empty string when no sources exist.
text = " <span color= \" #6fffa3 \" >Destinations:</span> "
if routine . sources . len ! = 0 :
xAlign = 0
for it , routineSource in routine . sources :
useMarkup = true
Box :
#routineSeq returns an empty string when no destinations exist.
orient = OrientX
if routine . destinations . len ! = 0 :
spacing = 6
for it , routineDestination in routine . destinations :
margin = 6
Box :
Label :
orient = OrientX
text = routineSource
spacing = 6
xAlign = 0
margin = 6
else :
Label :
Box :
text = routineDestination
orient = OrientX
xAlign = 0
spacing = 6
else :
margin = 6
Box :
Label :
orient = OrientX
text = " <span color= \" #ff6b6b \" >Routine will be ignored. Source required.</span> "
spacing = 6
xAlign = 0
margin = 6
useMarkup = true
Label :
Label :
text = " <span color= \" #ff6b6b \" >Routine will be ignored. Destination required.</span> "
text = " <span color= \" #6fffa3 \" >Destinations:</span> "
xAlign = 0
xAlign = 0
useMarkup = true
useMarkup = true
Box {. expand : false . } :
#routineSeq returns an empty string when no destinations exist.
orient = OrientX
if routine . destinations . len ! = 0 :
spacing = 6
for it , routineDestination in routine . destinations :
margin = 6
Box :
Label {. expand : false . } :
orient = OrientX
text = " <span size= \" large \" >Status: </span> "
spacing = 6
xAlign = 0
margin = 6
useMarkup = true
Label :
Label :
text = routineDestination
if list . runStatus ! = " " :
xAlign = 0
text = list . runStatus
else :
else :
Box :
text = " <span color= \" #6AC9FF \" size= \" large \" >Waiting patiently...</span> "
orient = OrientX
xAlign = 0
spacing = 6
margin = 6
margin = 6
useMarkup = true
Label :
# Run Button
text = " <span color= \" #ff6b6b \" >Routine will be ignored. Destination required.</span> "
Button {. expand : false . } :
xAlign = 0
Box :
useMarkup = true
orient = OrientX
else :
spacing = 6
Box {. expand : false . } :
orient = OrientY
margin = 6
margin = 6
Icon :
spacing = 12
name = " media-floppy "
Label :
pixelSize = 40
text = " <span size= \" large \" >You don ' t have any Bkup Routines!!! 🙂</span> "
margin = 4
Label {. expand : false . } :
text = " <span size= \" large \" >Run Bkup!</span> "
xAlign = 0
xAlign = 0
useMarkup = true
useMarkup = true
proc clicked ( ) =
Label :
#check that every source and destination exists for each routine or don't run bkup and show err status.
text = " <span size= \" large \" >Please click on the + Routine button above to create your first Routine.</span> "
#locations not found will be logged. see routine.nim
xAlign = 0
var allLocationsExist : seq [ bool ]
useMarkup = true
for routine in list . routineModel . routineSeq ( ) :
if routine . id in list . selected :
if list . routineModel . routineSeq ( ) . len ( ) > 0 :
if locationsExist ( routine ) = = true :
Box {. expand : false . } :
allLocationsExist . add ( true )
orient = OrientY
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 . } :
Box {. expand : false . } :
orient = OrientY
orient = OrientY
margin = 6
Box {. expand : false . } :
spacing = 12
orient = OrientX
Label :
spacing = 6
text = " <span size= \" large \" >You don ' t have any Bkup Routines!!! 🙂</span> "
margin = 6
xAlign = 0
Label {. expand : false . } :
useMarkup = true
text = " <span size= \" large \" >Status: </span> "
Label :
xAlign = 0
text = " <span size= \" large \" >Please click on the + Routine button above to create your first Routine.</span> "
useMarkup = true
xAlign = 0
Label :
useMarkup = true
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
export RoutineList