Skip to content

Commit

Permalink
schedules
Browse files Browse the repository at this point in the history
  • Loading branch information
Adis Durakovic committed Jan 4, 2024
1 parent 19092d3 commit f65e2e7
Show file tree
Hide file tree
Showing 20 changed files with 290 additions and 81 deletions.
134 changes: 106 additions & 28 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,35 @@ import (
"log"
"os"
"resticity/restic"
"sync"
"time"

"github.com/adrg/xdg"
"github.com/energye/systray"
"github.com/energye/systray/icon"
"github.com/go-co-op/gocron/v2"
"github.com/google/uuid"
"github.com/wailsapp/wails/v2/pkg/runtime"
)

// App struct
type App struct {
ctx context.Context
scheduler gocron.Scheduler
ctx context.Context
jmu sync.Mutex
scheduler gocron.Scheduler
runningJobs []BackupJob
}

type BackupJob struct {
BackupId string `json:"backup_id"`
RepositoryId string `json:"repository_id"`
JobId uuid.UUID `json:"job_id"`
}

type Settings struct {
Repositories []restic.Repository `json:"repositories"`
Backups []restic.Backup `json:"backups"`
Schedules []restic.Schedule `json:"schedules"`
}

func settingsFile() string {
Expand Down Expand Up @@ -57,17 +69,18 @@ func (a *App) systemTray() {
fmt.Println(len(ico))

systray.SetTitle("resticity")
systray.SetTooltip("yeaaah baby")
systray.SetTooltip("Resticity")

show := systray.AddMenuItem("Show", "Show The Window")
show := systray.AddMenuItem("Open resticity", "Show the main window")
systray.AddSeparator()
// exit := systray.AddMenuItem("Exit", "Quit The Program")cd

exit := systray.AddMenuItem("Quit", "Quit resticity")

show.Click(func() {

runtime.WindowToggleMaximise(a.ctx)
runtime.WindowShow(a.ctx)
})
// exit.Click(func() { os.Exit(0) })
exit.Click(func() { os.Exit(0) })

systray.SetOnClick(func(menu systray.IMenu) { runtime.WindowShow(a.ctx) })
// systray.SetOnRClick(func(menu systray.IMenu) { menu.ShowMenu() })
Expand All @@ -92,8 +105,11 @@ func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, It's show time!", name)
}

func (a *App) GetBackupJobs() []BackupJob {
return a.runningJobs
}

func (a *App) Snapshots(id string) []restic.Snapshot {
fmt.Println("IIIID", id)
s := GetSettings()
var r *restic.Repository
for i := range s.Repositories {
Expand All @@ -114,7 +130,7 @@ func (a *App) Settings() Settings {
}

func (a *App) SaveSettings(data Settings) {

a.RescheduleBackups()
fmt.Println("Saving settings")
if str, err := json.MarshalIndent(data, " ", " "); err == nil {
fmt.Println("Settings saved")
Expand All @@ -128,34 +144,96 @@ func (a *App) SaveSettings(data Settings) {
}
}

func (a *App) StopBackup(id uuid.UUID) {
a.scheduler.RemoveJob(id)
a.RescheduleBackups()
}

func (a *App) RescheduleBackups() {
s := GetSettings()
for b := range s.Backups {
var job gocron.Job
for j := range a.scheduler.Jobs() {
if a.scheduler.Jobs()[j].Name() == s.Backups[b].Name {
job = a.scheduler.Jobs()[j]
break
}
}
if job != nil {
a.scheduler.RemoveJob(job.ID())
}
for _, b := range s.Backups {

// todo: run missed backups
/*
- get last snapshot
- parse cron as date/duration whatever
- compare last snapshot date with cron date and job.NextRun()
*/

a.scheduler.NewJob(
gocron.CronJob("*/1 * * * *", false),
gocron.NewTask(func(backup restic.Backup) {
log.Print("doing job", backup.Name)
}, s.Backups[b]),
gocron.WithTags("backup", "bdonis"),
gocron.WithName(s.Backups[b].Name),
)
for _, t := range b.Targets {

jobName := "BACKUP-" + b.Id + "-TARGET-" + t
var job gocron.Job
for j := range a.scheduler.Jobs() {
if a.scheduler.Jobs()[j].Name() == jobName {
job = a.scheduler.Jobs()[j]
break
}
}
if job != nil {
a.scheduler.RemoveJob(job.ID())
}
if b.Cron == "" {
continue
}
a.scheduler.NewJob(
gocron.CronJob(b.Cron, false),
gocron.NewTask(func(backup restic.Backup) {
// actual backup
log.Print("doing job", backup.Name)
time.Sleep(30 * time.Second)
}, b),
gocron.WithTags("backup:"+b.Id, "repository:"+t),
gocron.WithName(jobName),
gocron.WithEventListeners(
gocron.BeforeJobRuns(func(jobID uuid.UUID, jobName string) {
a.jmu.Lock()
defer a.jmu.Unlock()
a.runningJobs = append(
a.runningJobs,
BackupJob{
BackupId: b.Id,
RepositoryId: t,
JobId: jobID,
},
)
}),
gocron.AfterJobRuns(
func(jobID uuid.UUID, jobName string) {
a.jmu.Lock()
defer a.jmu.Unlock()
for i := range a.runningJobs {
if a.runningJobs[i].BackupId == b.Id &&
a.runningJobs[i].RepositoryId == t {
a.runningJobs = append(
a.runningJobs[:i],
a.runningJobs[i+1:]...)
break
}
}
// do something after the job completes

},
),
gocron.AfterJobRunsWithError(
func(jobID uuid.UUID, jobName string, err error) {
a.jmu.Lock()
defer a.jmu.Unlock()
for i := range a.runningJobs {
if a.runningJobs[i].BackupId == b.Id &&
a.runningJobs[i].RepositoryId == t {
a.runningJobs = append(
a.runningJobs[:i],
a.runningJobs[i+1:]...)
break
}
}
},
),
),
)
}

}

}
Expand Down
1 change: 0 additions & 1 deletion frontend/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

<script setup lang="ts">
onMounted(() => {
console.log('MOUNTED')
useSettings().init()
})
</script>
Expand Down
51 changes: 43 additions & 8 deletions frontend/components/Backup/ExcludeOptions.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="collapse bg-base-200 mb-5">
<input type="radio" name="backup-accordion" />
<input type="radio" name="backup-accordion" checked />
<h3 class="collapse-title m-0 text-error"><FaIcon icon="folder-minus" class="mr-2" />Exclude</h3>
<div class="collapse-content">
<div class="grid grid-cols-2 gap-10">
Expand Down Expand Up @@ -44,14 +44,42 @@
</template>

<script lang="ts" setup>
const filesAndFolders = ref('')
const ifPresent = ref('')
const listedInFiles = ref('')
const cacheDir = ref(false)
const largerThan = ref(0)
const largerThanUnit = ref('K')
const props = defineProps({
excludes: {
default: [[]],
},
})
const filesAndFolders = ref(fromPropsArray('--exclude'))
const ifPresent = ref(fromPropsArray('--exclude-if-present'))
const listedInFiles = ref(fromPropsArray('--exclude-file'))
const cacheDir = ref(props.excludes.some((e) => e[0] === '--exclude-caches'))
const largerThan = ref(fromPropsArray('--exclude-if-larger-than', '').replace(/[^0-9]/g, '') || 0)
const largerThanUnit = ref(fromPropsArray('--exclude-if-larger-than', '').replace(/[0-9]/g, '') || 'K')
const emit = defineEmits(['update'])
// onMounted(() => {
//
// listedInFiles.value = props.excludes
// .filter((e) => e[0] === '--exclude-file')
// .map((e) => e[1])
// .join('\n')
// cacheDir.value = props.excludes.some((e) => e[0] === '--exclude-caches')
// largerThan.value =
// parseInt(
// props.excludes
// .filter((e) => e[0] === '--exclude-if-larger-than')
// .map((e) => e[1])
// .join('')
// .replace(/[^0-9]/g, '')
// ) || 0
// largerThanUnit.value = props.excludes
// .filter((e) => e[0] === '--exclude-if-larger-than')
// .map((e) => e[1])
// .join('')
// .replace(/[0-9]/g, '')
// })
function toParamArray(str: string, param: string): any {
return str
.split('\n')
Expand All @@ -60,13 +88,20 @@
.map((f) => [param, f])
}
function fromPropsArray(param: string, j: string = '\n'): string {
return props.excludes
.filter((e) => e[0] === param)
.map((e) => e[1])
.join(j)
}
watch([filesAndFolders, ifPresent, listedInFiles, cacheDir, largerThan, largerThanUnit], () => {
emit('update', [
...toParamArray(filesAndFolders.value, '--exclude'),
...toParamArray(ifPresent.value, '--exclude-if-present'),
...toParamArray(listedInFiles.value, '--exclude-file'),
...(cacheDir.value ? [['--exclude-caches', '']] : []),
...(largerThan.value > 0 ? [['--exclude-if-larger-than', `${largerThan.value}${largerThanUnit.value}`]] : []),
...(parseInt(largerThan.value as string) > 0 ? [['--exclude-if-larger-than', `${largerThan.value}${largerThanUnit.value}`]] : []),
])
})
</script>
3 changes: 3 additions & 0 deletions frontend/components/Backup/List.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<div class="p-5">
<h3 class="m-0 text-info"><FaIcon icon="folder-open" class="mr-2" />{{ backup.name }}</h3>
<p class="text-sm">{{ backup.path }}</p>
<div class="flex animate-pulse" v-if="useJobs().backupIsRunning(backup.id)">
<span class="loading loading-infinity loading-sm text-warning"></span><span class="text-sm ml-2 text-warning">Backup running</span>
</div>
</div>
</NuxtLink>
</div>
Expand Down
32 changes: 26 additions & 6 deletions frontend/components/Backup/ScheduleOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,35 @@
<div class="collapse-content">
<p>Set a cronjob for this backup.</p>
<div class="join">
<select class="select select-bordered select-sm join-item w-32">
<option value="* * * * *" selected>Never</option>
<option value="* * * * *">Every hour</option>
<option value="* * * * *">Every 2 hours</option>
<option value="* * * * *">Every day</option>
<select class="select select-bordered select-sm join-item w-48" v-model="predefined">
<option value="">Never</option>
<option value="* * * * *">Every minute</option>
<option value="0 * * * *">Every hour</option>
<option value="0 */2 * * *">Every 2 hours</option>
<option value="0 0 * * *">Every day</option>
<option value="0 8 * * *">Every day at 8 am</option>
<option value="custom">Custom</option>
</select>
<input class="input input-bordered join-item input-sm w-48 disabled:input-bordered" placeholder="0" disabled />
<input class="input input-bordered join-item input-sm w-48 disabled:input-bordered" placeholder="" :disabled="predefined !== 'custom'" v-model="cron" />
</div>
</div>
</div>
</template>

<script lang="ts" setup>
const props = defineProps({
cron: {
type: String,
default: '',
},
})
const predefined = ref<string>(props.cron === '' ? '' : 'custom')
const cron = ref<string>(props.cron)
const emit = defineEmits(['update'])
watch([predefined, cron], () => {
if (predefined.value !== 'custom') {
cron.value = predefined.value
}
emit('update', cron.value)
})
</script>
17 changes: 12 additions & 5 deletions frontend/components/Backup/Targets.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
<template>
<div class="collapse bg-base-200 mb-5">
<input type="radio" name="backup-accordion" checked />
<input type="radio" name="backup-accordion" />
<h3 class="collapse-title m-0 text-success"><FaIcon icon="server" class="mr-2" />Targets</h3>

<div class="collapse-content">
<div class="grid grid-cols-5">
<div class="grid grid-cols-5 gap-5">
<div
v-for="repo in useSettings().settings?.repositories"
class="shadow-lg bg-base-300 rounded-lg no-underline hover:bg-primary transition-all hover:bg-opacity-10 cursor-pointer"
@click="toggleTarget(repo.id)"
:key="repo.id"
>
<div class="p-5">
<div class="p-3">
<div class="form-control">
<span class="label cursor-pointer justify-normal">
<input type="checkbox" :checked="isSelected(repo.id)" class="checkbox checkbox-xs mr-3" :class="isSelected(repo.id) ? 'checkbox-info' : ''" />
<span class="label-text" :class="isSelected(repo.id) ? 'text-info' : ''"><FaIcon icon="fa-hard-drive" class="mr-2" size="sm" />{{ repo.name }}</span>
</span>
</div>
<p class="text-xs break-words m-0">{{ repo.path }}</p>
<p class="text-xs break-words m-0 opacity-40">{{ repo.path }}</p>
</div>
</div>
</div>
Expand All @@ -26,7 +27,13 @@
</template>

<script setup lang="ts">
const targets = ref<string[]>([])
const props = defineProps({
targets: {
type: Array as PropType<string[]>,
default: [],
},
})
const targets = ref<string[]>(props.targets)
const emit = defineEmits(['update'])
function isSelected(id: string) {
return targets.value.includes(id)
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/HeaderNavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<NuxtLink to="/backups" :class="useRoute().path.includes('backups') ? 'active' : ''"><FaIcon icon="fa-upload" />Backups</NuxtLink>
</li>
<li>
<NuxtLink to="/settings"><FaIcon icon="fa-gears" />Settings</NuxtLink>
<NuxtLink to="/schedules" :class="useRoute().path.includes('schedules') ? 'active' : ''"><FaIcon icon="clock" />Schedules</NuxtLink>
</li>
</ul>
</div>
Expand Down
Loading

0 comments on commit f65e2e7

Please sign in to comment.