Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions schtasks/schtasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,20 @@ func listRegisteredTasks() ([]byte, error) {
return stdout.Bytes(), err
}

func deleteTask(taskName string) (string, error) {
func deleteTask(taskName string) error {
taskName, err := sanitizeTaskName(taskName)
if err != nil {
return "", err
return err
}
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
cmd := exec.CommandContext(context.TODO(), binaryPath, "/delete", "/f", "/tn", taskName)
cmd.Stdout = stdout
cmd.Stdout = stdout // we're not actually interested in the output, but we need to capture it to avoid it being printed to the console
cmd.Stderr = stderr
err = cmd.Run()
if err != nil {
return "", schTasksError(stderr.String())
return schTasksError(stderr.String())
}
return stdout.String(), nil
return nil
}

// readTaskInfo returns the raw output from querying the task name (via schtasks.exe)
Expand Down
45 changes: 28 additions & 17 deletions schtasks/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ type Task struct {
Principals Principals `xml:"Principals"`
Settings Settings `xml:"Settings"`
Actions Actions `xml:"Actions"`
fromNow time.Time `xml:"-"`
}

func NewTask() Task {
func NewTask(options ...TaskOption) Task {
var userID string
if currentUser, err := user.Current(); err == nil {
userID = currentUser.Uid
Expand All @@ -42,7 +43,7 @@ func NewTask() Task {
Version: taskSchemaVersion,
Xmlns: taskSchema,
RegistrationInfo: RegistrationInfo{
Date: time.Now().Format(dateFormat),
Date: time.Now().Format(dateFormat), // this will be overridden by setFromNow if needed
Author: constants.ApplicationName,
},
Principals: Principals{
Expand All @@ -69,6 +70,11 @@ func NewTask() Task {
Actions: Actions{
Context: author,
},
fromNow: time.Now(), // default will be overridden by setFromNow if needed
}

for _, option := range options {
option.apply(&task)
}
return task
}
Expand Down Expand Up @@ -105,9 +111,14 @@ func (t *Task) AddSchedules(schedules []*calendar.Event) {
}
}

func (t *Task) setFromNow(fromNow time.Time) {
t.fromNow = fromNow
t.RegistrationInfo.Date = fromNow.Format(dateFormat)
}

func (t *Task) addTimeTrigger(triggerOnce time.Time) {
timeTrigger := TimeTrigger{
StartBoundary: triggerOnce.Format(dateFormat),
StartBoundary: &triggerOnce,
}
if t.Triggers.TimeTrigger == nil {
t.Triggers.TimeTrigger = []TimeTrigger{timeTrigger}
Expand All @@ -125,7 +136,7 @@ func (t *Task) addCalendarTrigger(trigger CalendarTrigger) {
}

func (t *Task) addDailyTrigger(schedule *calendar.Event) {
start := schedule.Next(time.Now())
start := schedule.Next(t.fromNow)
// get all recurrences in the same day
recurrences := schedule.GetAllInBetween(start, start.Add(24*time.Hour))
if len(recurrences) == 0 {
Expand All @@ -135,7 +146,7 @@ func (t *Task) addDailyTrigger(schedule *calendar.Event) {
// Is it only once a day?
if len(recurrences) == 1 {
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: recurrences[0].Format(dateFormat),
StartBoundary: &recurrences[0],
ScheduleByDay: &ScheduleByDay{
DaysInterval: 1,
},
Expand All @@ -149,7 +160,7 @@ func (t *Task) addDailyTrigger(schedule *calendar.Event) {
// case with regular repetition
interval := period.NewOf(compactDifferences[0])
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: start.Format(dateFormat),
StartBoundary: &start,
ScheduleByDay: &ScheduleByDay{
DaysInterval: 1,
},
Expand All @@ -166,9 +177,9 @@ func (t *Task) addDailyTrigger(schedule *calendar.Event) {
return
}
// install them all
for _, recurrence := range recurrences {
for i := range recurrences {
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: recurrence.Format(dateFormat),
StartBoundary: &recurrences[i],
ScheduleByDay: &ScheduleByDay{
Comment thread
creativeprojects marked this conversation as resolved.
DaysInterval: 1,
},
Expand All @@ -177,7 +188,7 @@ func (t *Task) addDailyTrigger(schedule *calendar.Event) {
}

func (t *Task) addWeeklyTrigger(schedule *calendar.Event) {
start := schedule.Next(time.Now())
start := schedule.Next(t.fromNow)
// get all recurrences in the same day
recurrences := schedule.GetAllInBetween(start, start.Add(24*time.Hour))
if len(recurrences) == 0 {
Expand All @@ -187,7 +198,7 @@ func (t *Task) addWeeklyTrigger(schedule *calendar.Event) {
// Is it only once per 24h?
if len(recurrences) == 1 {
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: recurrences[0].Format(dateFormat),
StartBoundary: &recurrences[0],
ScheduleByWeek: &ScheduleByWeek{
WeeksInterval: 1,
DaysOfWeek: convertWeekdays(schedule.WeekDay.GetRangeValues()),
Expand All @@ -202,7 +213,7 @@ func (t *Task) addWeeklyTrigger(schedule *calendar.Event) {
// case with regular repetition
interval := period.NewOf(compactDifferences[0])
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: start.Format(dateFormat),
StartBoundary: &start,
ScheduleByWeek: &ScheduleByWeek{
WeeksInterval: 1,
DaysOfWeek: convertWeekdays(schedule.WeekDay.GetRangeValues()),
Expand All @@ -220,9 +231,9 @@ func (t *Task) addWeeklyTrigger(schedule *calendar.Event) {
return
}
// install them all
for _, recurrence := range recurrences {
for i := range recurrences {
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: recurrence.Format(dateFormat),
StartBoundary: &recurrences[i],
ScheduleByWeek: &ScheduleByWeek{
Comment thread
creativeprojects marked this conversation as resolved.
WeeksInterval: 1,
DaysOfWeek: convertWeekdays(schedule.WeekDay.GetRangeValues()),
Expand All @@ -232,7 +243,7 @@ func (t *Task) addWeeklyTrigger(schedule *calendar.Event) {
}

func (t *Task) addMonthlyTrigger(schedule *calendar.Event) {
start := schedule.Next(time.Now())
start := schedule.Next(t.fromNow)
// get all recurrences in the same day
recurrences := schedule.GetAllInBetween(start, start.Add(24*time.Hour))
if len(recurrences) == 0 {
Expand All @@ -245,14 +256,14 @@ func (t *Task) addMonthlyTrigger(schedule *calendar.Event) {
return
}
// install them all
for _, recurrence := range recurrences {
for i := range recurrences {
if schedule.WeekDay.HasValue() && schedule.Day.HasValue() {
clog.Warningf("task scheduler does not support a day of the month and a day of the week in the same trigger: %s", schedule.String())
return
}
if schedule.WeekDay.HasValue() {
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: recurrence.Format(dateFormat),
StartBoundary: &recurrences[i],
ScheduleByMonthDayOfWeek: &ScheduleByMonthDayOfWeek{
DaysOfWeek: convertWeekdays(schedule.WeekDay.GetRangeValues()),
Weeks: AllWeeks,
Expand All @@ -262,7 +273,7 @@ func (t *Task) addMonthlyTrigger(schedule *calendar.Event) {
continue
}
t.addCalendarTrigger(CalendarTrigger{
StartBoundary: recurrence.Format(dateFormat),
StartBoundary: &recurrences[i],
ScheduleByMonth: &ScheduleByMonth{
DaysOfMonth: convertDaysOfMonth(schedule.Day.GetRangeValues()),
Months: convertMonths(schedule.Month.GetRangeValues()),
Expand Down
21 changes: 21 additions & 0 deletions schtasks/task_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build windows

package schtasks

import "time"

type TaskOption interface {
apply(t *Task)
}

type WithFromNowOption struct {
now time.Time
}

func WithFromNow(now time.Time) WithFromNowOption {
return WithFromNowOption{now: now}
}

func (w WithFromNowOption) apply(t *Task) {
t.setFromNow(w.now)
}
17 changes: 10 additions & 7 deletions schtasks/taskscheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"slices"
"strings"
"text/tabwriter"
"time"

"github.com/creativeprojects/clog"
"github.com/creativeprojects/resticprofile/calendar"
Expand All @@ -48,13 +49,12 @@ func Create(config *Config, schedules []*calendar.Event, permission Permission)
taskPath := getTaskPath(config.ProfileName, config.CommandName)
if slices.Contains(list, taskPath) {
clog.Debugf("task %q already exists: deleting before creating", taskPath)
_, err = deleteTask(taskPath)
err = deleteTask(taskPath)
if err != nil {
return fmt.Errorf("cannot delete existing task to replace it: %w", err)
}
}

task := createTaskDefinition(config, schedules)
task := createTaskDefinition(config, schedules, time.Time{})
task.RegistrationInfo.URI = taskPath

switch config.RunLevel {
Expand Down Expand Up @@ -112,8 +112,7 @@ func Create(config *Config, schedules []*calendar.Event, permission Permission)
// Delete a task
func Delete(title, subtitle string) error {
taskName := getTaskPath(title, subtitle)
_, err := deleteTask(taskName)
return err
return deleteTask(taskName)
}

// Status returns the status of a task
Expand Down Expand Up @@ -181,8 +180,12 @@ func getTaskPath(profileName, commandName string) string {
return fmt.Sprintf("%s%s %s", tasksPathPrefix, profileName, commandName)
}

func createTaskDefinition(config *Config, schedules []*calendar.Event) Task {
task := NewTask()
func createTaskDefinition(config *Config, schedules []*calendar.Event, from time.Time) Task {
options := make([]TaskOption, 0, 1)
if !from.IsZero() {
options = append(options, WithFromNow(from))
}
task := NewTask(options...)
task.RegistrationInfo.Description = config.JobDescription
task.Settings.StartWhenAvailable = config.StartWhenAvailable
task.AddExecAction(ExecAction{
Expand Down
Loading
Loading