src/WebJobs.Extensions/Extensions/Timers/Scheduling/FileSystemScheduleMonitor.cs (114 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.IO; using System.Threading.Tasks; using Newtonsoft.Json; namespace Microsoft.Azure.WebJobs.Extensions.Timers { /// <summary> /// This class is used to monitor and record schedule occurrences. It stores /// schedule occurrence info to the file system at runtime. /// <see cref="TimerTriggerAttribute"/> uses this class to monitor /// schedules to avoid missing scheduled executions. /// </summary> public class FileSystemScheduleMonitor : ScheduleMonitor { private readonly JsonSerializer _serializer; private string _statusFilePath; /// <summary> /// Constructs a new instance /// </summary> public FileSystemScheduleMonitor() : this(Directory.GetCurrentDirectory()) { } /// <summary> /// Constructs a new instance /// </summary> public FileSystemScheduleMonitor(string currentDirectory) { if (string.IsNullOrEmpty(currentDirectory)) { throw new ArgumentNullException("currentDirectory"); } // default to the D:\HOME\DATA directory when running in Azure WebApps string home = Environment.GetEnvironmentVariable("HOME"); string rootPath = string.Empty; if (!string.IsNullOrEmpty(home)) { rootPath = Path.Combine(home, "data"); // Determine the path to the WebJobs folder, so we can write our status // files there. We leverage the fact that the TEMP directory structure we // run from is the same as the data directory structure int start = currentDirectory.IndexOf("jobs", StringComparison.OrdinalIgnoreCase); int end = currentDirectory.LastIndexOf(Path.DirectorySeparatorChar); if (start > 0 && end > 0) { string jobPath = currentDirectory.Substring(start, end - start); _statusFilePath = Path.Combine(rootPath, jobPath); } } else { rootPath = Path.GetTempPath(); } if (string.IsNullOrEmpty(_statusFilePath) || !Directory.Exists(_statusFilePath)) { _statusFilePath = Path.Combine(rootPath, @"webjobs\timers"); } Directory.CreateDirectory(_statusFilePath); JsonSerializerSettings settings = new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.IsoDateFormat }; _serializer = JsonSerializer.Create(settings); } /// <summary> /// Gets or sets the path where schedule status files will be written. /// </summary> public string StatusFilePath { get { return _statusFilePath; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("value"); } if (!Directory.Exists(value)) { throw new ArgumentException("The specified path does not exist.", "value"); } _statusFilePath = value; } } /// <inheritdoc/> public override Task<ScheduleStatus> GetStatusAsync(string timerName) { string statusFilePath = GetStatusFileName(timerName); if (!File.Exists(statusFilePath)) { return Task.FromResult<ScheduleStatus>(null); } ScheduleStatus status; string statusLine = File.ReadAllText(statusFilePath); using (StringReader stringReader = new StringReader(statusLine)) { status = (ScheduleStatus)_serializer.Deserialize(stringReader, typeof(ScheduleStatus)); } return Task.FromResult<ScheduleStatus>(status); } /// <inheritdoc/> public override Task UpdateStatusAsync(string timerName, ScheduleStatus status) { string statusLine; using (StringWriter stringWriter = new StringWriter()) { _serializer.Serialize(stringWriter, status); statusLine = stringWriter.ToString(); } string statusFileName = GetStatusFileName(timerName); try { File.WriteAllText(statusFileName, statusLine); } catch { // best effort } return Task.FromResult(true); } /// <summary> /// Returns the schedule status file name for the specified timer /// </summary> /// <param name="timerName">The timer name</param> /// <returns></returns> protected internal string GetStatusFileName(string timerName) { return Path.Combine(StatusFilePath, timerName + ".status"); } } }