/// <summary> /// The actual scheduling procedure /// </summary> private void Runner() { var scheduled = new Dictionary <long, DateTime>(); while (!m_terminate) { //TODO: As this is executed repeatedly we should cache it // to avoid frequent db lookups //Determine schedule list var lst = Program.DataConnection.Schedules; foreach (var sc in lst) { if (!string.IsNullOrEmpty(sc.Repeat)) { DateTime start; DateTime last = new DateTime(0, DateTimeKind.Utc); if (!scheduled.TryGetValue(sc.ID, out start)) { start = new DateTime(sc.Time.Ticks, DateTimeKind.Utc); last = sc.LastRun; } try { start = GetNextValidTime(start, last, sc.Repeat, sc.AllowedDays); } catch (Exception ex) { Program.DataConnection.LogError(sc.ID.ToString(), "Scheduler failed to find next date", ex); } //If time is exceeded, run it now if (start <= DateTime.UtcNow) { var jobsToRun = new List <Server.Runner.IRunnerData>(); //TODO: Cache this to avoid frequent lookups foreach (var id in Program.DataConnection.GetBackupIDsForTags(sc.Tags).Distinct().Select(x => x.ToString())) { //See if it is already queued var tmplst = from n in m_worker.CurrentTasks where n.Operation == Duplicati.Server.Serialization.DuplicatiOperation.Backup select n.Backup; var tastTemp = m_worker.CurrentTask; if (tastTemp != null && tastTemp.Operation == Duplicati.Server.Serialization.DuplicatiOperation.Backup) { tmplst.Union(new [] { tastTemp.Backup }); } //If it is not already in queue, put it there if (!tmplst.Any(x => x.ID == id)) { var entry = Program.DataConnection.GetBackup(id); if (entry != null) { jobsToRun.Add(Server.Runner.CreateTask(Duplicati.Server.Serialization.DuplicatiOperation.Backup, entry)); } } } //Caluclate next time, by adding the interval to the start until we have // passed the current date and time //TODO: Make this more efficient int i = 50000; while (start <= DateTime.UtcNow && i-- > 0) { try { start = GetNextValidTime(start, start.AddSeconds(1), sc.Repeat, sc.AllowedDays); } catch (Exception ex) { Program.DataConnection.LogError(sc.ID.ToString(), "Scheduler failed to find next date", ex); continue; } } Server.Runner.IRunnerData lastJob = jobsToRun.LastOrDefault(); if (lastJob != null && lastJob != null) { lock (m_lock) m_updateTasks[lastJob] = new Tuple <ISchedule, DateTime, DateTime>(sc, start, DateTime.UtcNow); } foreach (var job in jobsToRun) { m_worker.AddTask(job); } if (start < DateTime.UtcNow) { //TODO: Report this somehow continue; } } scheduled[sc.ID] = start; } } var existing = lst.ToDictionary(x => x.ID); //Sort them, lock as we assign the m_schedule variable lock (m_lock) m_schedule = (from n in scheduled where existing.ContainsKey(n.Key) orderby n.Value select new KeyValuePair <DateTime, ISchedule>(n.Value, existing[n.Key])).ToArray(); // Remove unused entries foreach (var c in (from n in scheduled where !existing.ContainsKey(n.Key) select n.Key).ToArray()) { scheduled.Remove(c); } //Raise event if needed if (NewSchedule != null) { NewSchedule(this, null); } int waittime = 0; //Figure out a sensible amount of time to sleep the thread if (scheduled.Count > 0) { //When is the next run scheduled? TimeSpan nextrun = scheduled.Min((x) => x.Value) - DateTime.UtcNow; if (nextrun.TotalMilliseconds < 0) { continue; } //Don't sleep for more than 5 minutes waittime = (int)Math.Min(nextrun.TotalMilliseconds, 60 * 1000 * 5); } else { //No tasks, check back later waittime = 60 * 1000; } //Waiting on the event, enables a wakeup call from termination // never use waittime = 0 m_event.WaitOne(Math.Max(100, waittime), false); } }
/// <summary> /// The actual scheduling procedure /// </summary> private void Runner() { var scheduled = new Dictionary <long, KeyValuePair <long, DateTime> >(); while (!m_terminate) { //TODO: As this is executed repeatedly we should cache it // to avoid frequent db lookups //Determine schedule list var lst = Program.DataConnection.Schedules; foreach (var sc in lst) { if (!string.IsNullOrEmpty(sc.Repeat)) { KeyValuePair <long, DateTime> startkey; DateTime last = new DateTime(0, DateTimeKind.Utc); DateTime start; var scticks = sc.Time.Ticks; if (!scheduled.TryGetValue(sc.ID, out startkey) || startkey.Key != scticks) { start = new DateTime(scticks, DateTimeKind.Utc); last = sc.LastRun; } else { start = startkey.Value; } try { // Recover from timedrift issues by overriding the dates if the last run date is in the future. if (last > DateTime.UtcNow) { start = DateTime.UtcNow; last = DateTime.UtcNow; } start = GetNextValidTime(start, last, sc.Repeat, sc.AllowedDays); } catch (Exception ex) { Program.DataConnection.LogError(sc.ID.ToString(), "Scheduler failed to find next date", ex); } //If time is exceeded, run it now if (start <= DateTime.UtcNow) { var jobsToRun = new List <Server.Runner.IRunnerData>(); //TODO: Cache this to avoid frequent lookups foreach (var id in Program.DataConnection.GetBackupIDsForTags(sc.Tags).Distinct().Select(x => x.ToString())) { //See if it is already queued var tmplst = from n in m_worker.CurrentTasks where n.Operation == Duplicati.Server.Serialization.DuplicatiOperation.Backup select n.Backup; var tastTemp = m_worker.CurrentTask; if (tastTemp != null && tastTemp.Operation == Duplicati.Server.Serialization.DuplicatiOperation.Backup) { tmplst.Union(new [] { tastTemp.Backup }); } //If it is not already in queue, put it there if (!tmplst.Any(x => x.ID == id)) { var entry = Program.DataConnection.GetBackup(id); if (entry != null) { Dictionary <string, string> options = Duplicati.Server.Runner.GetCommonOptions(entry, Duplicati.Server.Serialization.DuplicatiOperation.Backup); Duplicati.Server.Runner.ApplyOptions(entry, Duplicati.Server.Serialization.DuplicatiOperation.Backup, options); if ((new Duplicati.Library.Main.Options(options)).DisableOnBattery && (Duplicati.Library.Utility.Power.PowerSupply.GetSource() == Duplicati.Library.Utility.Power.PowerSupply.Source.Battery)) { Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "BackupDisabledOnBattery", "Scheduled backup disabled while on battery power."); } else { jobsToRun.Add(Server.Runner.CreateTask(Duplicati.Server.Serialization.DuplicatiOperation.Backup, entry)); } } } } //Caluclate next time, by finding the first entry later than now try { start = GetNextValidTime(start, new DateTime(Math.Max(DateTime.UtcNow.AddSeconds(1).Ticks, start.AddSeconds(1).Ticks), DateTimeKind.Utc), sc.Repeat, sc.AllowedDays); } catch (Exception ex) { Program.DataConnection.LogError(sc.ID.ToString(), "Scheduler failed to find next date", ex); continue; } Server.Runner.IRunnerData lastJob = jobsToRun.LastOrDefault(); if (lastJob != null && lastJob != null) { lock (m_lock) m_updateTasks[lastJob] = new Tuple <ISchedule, DateTime, DateTime>(sc, start, DateTime.UtcNow); } foreach (var job in jobsToRun) { m_worker.AddTask(job); } if (start < DateTime.UtcNow) { //TODO: Report this somehow continue; } } scheduled[sc.ID] = new KeyValuePair <long, DateTime>(scticks, start); } } var existing = lst.ToDictionary(x => x.ID); //Sort them, lock as we assign the m_schedule variable lock (m_lock) m_schedule = (from n in scheduled where existing.ContainsKey(n.Key) orderby n.Value.Value select new KeyValuePair <DateTime, ISchedule>(n.Value.Value, existing[n.Key])).ToArray(); // Remove unused entries foreach (var c in (from n in scheduled where !existing.ContainsKey(n.Key) select n.Key).ToArray()) { scheduled.Remove(c); } //Raise event if needed if (NewSchedule != null) { NewSchedule(this, null); } int waittime = 0; //Figure out a sensible amount of time to sleep the thread if (scheduled.Count > 0) { //When is the next run scheduled? TimeSpan nextrun = scheduled.Values.Min((x) => x.Value) - DateTime.UtcNow; if (nextrun.TotalMilliseconds < 0) { continue; } //Don't sleep for more than 5 minutes waittime = (int)Math.Min(nextrun.TotalMilliseconds, 60 * 1000 * 5); } else { //No tasks, check back later waittime = 60 * 1000; } //Waiting on the event, enables a wakeup call from termination // never use waittime = 0 m_event.WaitOne(Math.Max(100, waittime), false); } }