// Factory method to create a new item processor based on the item type public static ItemProcessor Create(User user, UserStorageContext storage, Guid itemTypeID) { if (itemTypeID == SystemItemTypes.Contact) return new ContactProcessor(user, storage); if (itemTypeID == SystemItemTypes.Step) return new StepProcessor(user, storage); return null; }
// OBSOLETE! Stamping of CompletedOn field is being done on each client to get proper timezone and immediate response. // should only be called if newItem.Complete value is true public static bool ProcessUpdateCompletedOn(UserStorageContext userContext, Item oldItem, Item newItem) { var wasComplete = oldItem.GetFieldValue(FieldNames.Complete); if (wasComplete == null || !wasComplete.Value.Equals("true", StringComparison.OrdinalIgnoreCase)) { // timestamp the CompletedOn field newItem.GetFieldValue(FieldNames.CompletedOn, true).Value = DateTime.UtcNow.ToString(); } return false; }
public GoogleClient(User user, UserStorageContext storage) { // for using existing access token with renewal this.user = user; this.storage = storage; if (user.UserCredentials == null || user.UserCredentials.Count == 0) { // ensure UserCredentials are present this.user = storage.GetUser(user.ID, true); } UserCredential googleConsent = this.user.GetCredential(UserCredential.GoogleConsent); if (googleConsent != null) { this.googleAuthenticator = CreateGoogleAuthenticator(GetAccessToken); } }
public static bool DeleteItemChildrenRecursively(UserStorageContext storageContext, Item item) { var children = storageContext.Items.Where(i => i.ParentID == item.ID).ToList(); bool commit = false; foreach (var c in children) { DeleteItemChildrenRecursively(storageContext, c); storageContext.Items.Remove(c); commit = true; } // commit deletion of all children at the same layer together if (commit) { storageContext.SaveChanges(); } return commit; }
public static Item InstallActivity(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, User user, Folder category, Guid? subCategory, GalleryActivity galleryActivity) { // get activity from context var activity = suggestionsContext.GalleryActivities.FirstOrDefault(ga => ga.ID == galleryActivity.ID); if (activity == null) return null; if (category == null) { var galleryCategory = suggestionsContext.GalleryCategories.FirstOrDefault(gc => gc.ID == activity.CategoryID); if (galleryCategory == null) return null; category = CreateCategory(userContext, user, galleryCategory.Name); } return InstallActivity(userContext, suggestionsContext, category, subCategory, activity); }
public static bool DeleteItemReferences(User currentUser, UserStorageContext storageContext, Item item) { string itemID = item.ID.ToString(); var itemRefs = storageContext.Items.Include("FieldValues"). Where(i => i.UserID == currentUser.ID && i.ItemTypeID == SystemItemTypes.Reference && i.FieldValues.Any(fv => fv.FieldName == FieldNames.EntityRef && fv.Value == itemID)).ToList(); bool commit = false; foreach (var itemRef in itemRefs) { storageContext.Items.Remove(itemRef); commit = true; } // commit deletion of References if (commit) { storageContext.SaveChanges(); } return commit; }
/// <summary> /// Delete all workflow instances associated with this entity ID (the entity is already deleted) /// </summary> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <param name="entity"></param> public static void DeleteWorkflows(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, Guid entityID) { try { // get all the workflow instances for this Item var wis = suggestionsContext.WorkflowInstances.Where(w => w.EntityID == entityID).ToList(); if (wis.Count > 0) { // loop over the workflow instances and dispatch the new message foreach (var instance in wis) { suggestionsContext.WorkflowInstances.Remove(instance); } suggestionsContext.SaveChanges(); } } catch (Exception ex) { TraceLog.TraceException("DeleteWorkflows failed", ex); } }
public NextStepsDataModel(BaseController controller) { this.storageContext = controller.StorageContext; this.currentUser = controller.CurrentUser; }
/// <summary> /// Invoke the workflows for an operation depending on the environment. /// If in Azure, enqueue a message; otherwise, invoke the workflow host directly. /// </summary> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <param name="operation"></param> public static void InvokeWorkflowForOperation(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, Operation operation) { if (HostEnvironment.IsAzure) MessageQueue.EnqueueMessage(operation.ID); else ProcessOperation(userContext, suggestionsContext, operation); }
public AppointmentProcessor(User user, UserStorageContext storage) { this.user = user; this.storage = storage; }
public static Item InstallActivity(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, Folder category, Guid? subCategory, GalleryActivity galleryActivity) { Item result = null; try { // deserialize the definition var def = JsonSerializer.Deserialize<ActivityDefinition>(galleryActivity.Definition); // create the new item corresponding to the activity DateTime now = DateTime.Now; var activity = new Item() { ID = Guid.NewGuid(), Name = def.Name, FolderID = category.ID, ParentID = subCategory, UserID = category.UserID, ItemTypeID = SystemItemTypes.Activity, IsList = true, Status = StatusTypes.Active, Created = now, LastModified = now, }; result = activity; // make this the last activity in the (sub)category float sortOrder = (userContext.Items.Any(i => i.UserID == category.UserID && i.FolderID == category.ID && i.ItemTypeID == SystemItemTypes.Activity && (subCategory.HasValue ? i.ParentID == subCategory : i.ParentID == null)) ? userContext.Items.Where(i => i.UserID == category.UserID && i.FolderID == category.ID && i.ItemTypeID == SystemItemTypes.Activity && (subCategory.HasValue ? i.ParentID == subCategory : i.ParentID == null)). Select(i => i.SortOrder). Max() : 0f) + 1000f; activity.SortOrder = sortOrder; // set ActivityID of gallery activity this Item is created from activity.GetFieldValue(ExtendedFieldNames.ActivityID, true).Value = galleryActivity.ID.ToString(); // if provided, set the default cadence of the activity if (def.Recurrence != null) activity.GetFieldValue(FieldNames.Repeat, true).Value = def.Recurrence.ToString(); // if provided, set the status if (def.Status != null) activity.Status = def.Status; userContext.Items.Add(activity); userContext.SaveChanges(); // install all the steps for the activity sortOrder = 1; foreach (var step in def.Steps) { InstallStep(userContext, category, activity.ID, step, sortOrder); sortOrder += 1; } } catch (Exception ex) { TraceLog.TraceException("InstallActivity failed", ex); return result; } return result; }
/// <summary> /// Start a workflow of a certain type, passing it an entity and some instance data to start /// </summary> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <param name="type">String representing the workflow type</param> /// <param name="entity">Entity to associate with the workflow</param> /// <param name="instanceData">Instance data to pass into the workflow</param> public static void StartWorkflow(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, string type, ServerEntity entity, string instanceData) { WorkflowInstance instance = null; try { Workflow workflow = null; // get the workflow definition out of the database try { var wt = suggestionsContext.WorkflowTypes.Single(t => t.Type == type); workflow = JsonSerializer.Deserialize<Workflow>(wt.Definition); } catch (Exception ex) { TraceLog.TraceException("Could not find or deserialize workflow definition", ex); return; } // don't start a workflow with no states if (workflow.States.Count == 0) return; // store the database contexts workflow.UserContext = userContext; workflow.SuggestionsContext = suggestionsContext; // create the new workflow instance and store in the workflow DB DateTime now = DateTime.Now; instance = new WorkflowInstance() { ID = Guid.NewGuid(), EntityID = entity.ID, EntityName = entity.Name, WorkflowType = type, State = workflow.States[0].Name, InstanceData = instanceData ?? "{}", Created = now, LastModified = now, LockedBy = WorkflowHost.Me, }; suggestionsContext.WorkflowInstances.Add(instance); suggestionsContext.SaveChanges(); TraceLog.TraceInfo("Starting workflow " + type); // invoke the workflow and process steps until workflow is blocked for user input or is done workflow.Run(instance, entity); // unlock the workflowinstance instance.LockedBy = null; suggestionsContext.SaveChanges(); } catch (Exception ex) { TraceLog.TraceException("StartWorkflow failed", ex); if (instance != null && instance.LockedBy == WorkflowHost.Me) { // unlock the workflowinstance instance.LockedBy = null; suggestionsContext.SaveChanges(); } } }
/// <summary> /// Start workflows associated with a change in one or more of the entity's fields /// </summary> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <param name="entity"></param> /// <param name="oldEntity"></param> public static void StartTriggerWorkflows(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity, ServerEntity oldEntity) { if (entity == null || oldEntity == null) return; // only Item property triggers are supported at this time Item item = entity as Item; Item oldItem = oldEntity as Item; if (item != null) { // go through field by field, and if a field has changed, trigger the appropriate workflow ItemType itemType = userContext.ItemTypes.Include("Fields").Single(it => it.ID == item.ItemTypeID); foreach (var field in itemType.Fields) { object newValue = item.GetFieldValue(field); object oldValue = item.GetFieldValue(field); // skip fields that haven't changed if (newValue == null || newValue.Equals(oldValue)) continue; // do field-specific processing for select fields switch (field.Name) { case FieldNames.Name: //disable for now //RestartWorkflow(userContext, suggestionsContext, item, WorkflowNames.NewTask); break; } } } }
/// <summary> /// Start NewUser/NewFolder/NewItem workflows based on the entity type /// </summary> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <param name="entity"></param> public static void StartNewWorkflows(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity) { if (entity == null) return; // figure out what kind of entity this is Item item = entity as Item; Folder folder = entity as Folder; User user = entity as User; // verify there are no workflow instances associated with this item yet var wis = suggestionsContext.WorkflowInstances.Where(wi => wi.EntityID == entity.ID).ToList(); if (wis.Count > 0) return; if (item != null && item.IsList == false) { #if false if (item.ItemTypeID == SystemItemTypes.Task) StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewTask, item, null); // the Contact and Grocery new item processing happens in ItemProcessor now if (item.ItemTypeID == SystemItemTypes.Contact) StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewContact, item, null); if (item.ItemTypeID == SystemItemTypes.Grocery) Workflow.StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewGrocery, item, null); #endif } if (folder != null) { } if (user != null) { StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewUser, user, null); } }
/// <summary> /// Restart the workflows associated with an entity /// </summary> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <param name="entity"></param> /// <param name="workflowType"></param> public static void RestartWorkflow(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity, string workflowType) { if (entity == null || workflowType == null) return; try { // kill all existing workflows associated with this Item // TODO: also need to mark the suggestions associated with this workflow as stale so that they don't // show up for the item again. var runningWFs = suggestionsContext.WorkflowInstances.Where(wi => wi.EntityID == entity.ID).ToList(); if (runningWFs.Count > 0) { foreach (var wf in runningWFs) suggestionsContext.WorkflowInstances.Remove(wf); suggestionsContext.SaveChanges(); } StartWorkflow(userContext, suggestionsContext, workflowType, entity, null); } catch (Exception) { StartWorkflow(userContext, suggestionsContext, workflowType, entity, null); } }
/// <summary> /// Process the operation based on the underlying entity type and the operation type /// </summary> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <param name="operation"></param> /// <returns>true if processed, false if the instance was locked and the operation needs to be replayed</returns> public static bool ProcessOperation(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, Operation operation) { if (operation == null) return true; if (userContext == null) userContext = Storage.NewUserContext; if (suggestionsContext == null) suggestionsContext = Storage.NewSuggestionsContext; Guid entityID = operation.EntityID; string entityType = operation.EntityType.Trim(); string operationType = operation.OperationType.Trim(); TraceLog.Session = operation.Session; // try to get a strongly-typed entity (item, folder, user...) ServerEntity entity = null, oldEntity = null; bool process = true; // extract the underlying entity unless this is a delete operation (in which case it's already deleted) if (operationType != "DELETE") { try { switch (entityType) { case "Item": Item item = userContext.Items.Include("FieldValues").Single(i => i.ID == entityID); Item oldItem = JsonSerializer.Deserialize<Item>(operation.OldBody); entity = item; oldEntity = oldItem; break; case "Folder": Folder folder = userContext.Folders.Single(i => i.ID == entityID); entity = folder; break; case "User": User user = userContext.Users.Single(i => i.ID == entityID); entity = user; break; case "Suggestion": // if the entity passed in is a suggestion, this is a "meta" request - get the underlying Entity's // ID and type Suggestion suggestion = suggestionsContext.Suggestions.Single(s => s.ID == entityID); entityID = suggestion.EntityID; entityType = suggestion.EntityType; switch (entityType) { case "Item": entity = userContext.Items.Include("FieldValues").Single(i => i.ID == entityID); break; case "Folder": entity = userContext.Folders.Single(i => i.ID == entityID); break; case "User": entity = userContext.Users.Single(i => i.ID == entityID); break; } operationType = "SUGGESTION"; break; default: TraceLog.TraceError("Invalid Entity Type " + entityType); process = false; break; } } catch (Exception ex) { TraceLog.TraceException(String.Format("Could not retrieve {0}", entityType), ex); process = false; } } // launch new workflows based on the changes to the item if (process) { switch (operationType) { case "DELETE": DeleteWorkflows(userContext, suggestionsContext, entityID); return true; case "POST": StartNewWorkflows(userContext, suggestionsContext, entity); return ExecuteWorkflows(userContext, suggestionsContext, entity); case "PUT": StartTriggerWorkflows(userContext, suggestionsContext, entity, oldEntity); return ExecuteWorkflows(userContext, suggestionsContext, entity); case "SUGGESTION": return ExecuteWorkflows(userContext, suggestionsContext, entity); default: TraceLog.TraceError("Invalid Operation Type " + operationType); return true; } } return true; }
private static Folder CreateCategory(UserStorageContext userContext, User user, string name) { var category = userContext.Folders.FirstOrDefault(f => f.UserID == user.ID && f.Name == name); if (category == null) { category = new Folder() { ID = Guid.NewGuid(), Name = name, UserID = user.ID, ItemTypeID = SystemItemTypes.Category, }; // make this the last category in the list float sortOrder = (userContext.Folders.Any(i => i.UserID == user.ID && i.ItemTypeID == SystemItemTypes.Category) ? userContext.Folders.Where(i => i.UserID == user.ID && i.ItemTypeID == SystemItemTypes.Category). Select(i => i.SortOrder). Max() : 0f) + 1000f; category.SortOrder = sortOrder; userContext.Folders.Add(category); userContext.SaveChanges(); } return category; }
private static void InstallStep(UserStorageContext userContext, Folder category, Guid activity, ActivityStep activityStep, float sortOrder) { try { DateTime now = DateTime.Now; var status = (sortOrder == 1) ? StatusTypes.Active : null; var step = new Item() { ID = Guid.NewGuid(), Name = activityStep.Name, FolderID = category.ID, UserID = category.UserID, ParentID = activity, ItemTypeID = SystemItemTypes.Step, SortOrder = sortOrder * 1000f, Status = status, Created = now, LastModified = now, FieldValues = new List<FieldValue>() }; // TODO: calculate proper due date for first & last steps (based on recurrence setting) if (sortOrder == 1) { // TEMPORARY: set first step due date to today step.GetFieldValue(FieldNames.DueDate, true).Value = now.ToUniversalTime().ToLongDateString(); } step.GetFieldValue(FieldNames.ActionType, true).Value = activityStep.Action; userContext.Items.Add(step); userContext.SaveChanges(); } catch (Exception ex) { TraceLog.TraceException("InstallStep failed for step " + activityStep.Name, ex); } }
/// <summary> /// Execute the workflow instances associated with this entity /// </summary> /// <param name="entity"></param> /// <param name="userContext"></param> /// <param name="suggestionsContext"></param> /// <returns>true if processing happened (and the operation doesn't need to be processed again), /// false if one the workflow instances was locked (causes the message to be reprocessed)</returns> public static bool ExecuteWorkflows(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity) { if (entity == null) return true; List<WorkflowInstance> wis = null; try { // get all the workflow instances for this Item wis = suggestionsContext.WorkflowInstances.Where(w => w.EntityID == entity.ID).ToList(); if (wis.Count > 0) { // if the instance is locked by someone else, stop processing // otherwise lock each of the workflow instances foreach (var instance in wis) { if (instance.LockedBy != null && instance.LockedBy != Me) return false; instance.LockedBy = Me; } suggestionsContext.SaveChanges(); // reacquire the lock list and verify they were all locked by Me (if not, stop processing) // projecting locks and not workflow instances to ensure that the database's lock values are returned (not from EF's cache) var locks = suggestionsContext.WorkflowInstances.Where(w => w.EntityID == entity.ID).Select(w => w.LockedBy).ToList(); foreach (var lockedby in locks) if (lockedby != Me) return false; // loop over the workflow instances and dispatch the new message foreach (var instance in wis) { Workflow workflow = null; try { var wt = suggestionsContext.WorkflowTypes.Single(t => t.Type == instance.WorkflowType); workflow = JsonSerializer.Deserialize<Workflow>(wt.Definition); } catch (Exception ex) { TraceLog.TraceException("Could not find or deserialize workflow definition", ex); continue; } // set the database contexts workflow.UserContext = userContext; workflow.SuggestionsContext = suggestionsContext; // invoke the workflow and process steps until workflow is blocked for user input or is done workflow.Run(instance, entity); } } return true; } catch (Exception ex) { TraceLog.TraceException("ExecuteWorkflows failed", ex); return true; } finally { // find and unlock all remaining workflow instances that relate to this entity // note that a new context is used for this - to avoid caching problems where the current thread // believes it is the owner but the database says otherwise. var context = Storage.NewSuggestionsContext; wis = context.WorkflowInstances.Where(w => w.EntityID == entity.ID).ToList(); if (wis.Count > 0) { // unlock each of the workflow instances foreach (var instance in wis) if (instance.LockedBy == Me) instance.LockedBy = null; context.SaveChanges(); } } }
// may return either Folder or Item (caller should verify) public static object InstallCategory(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, User user, Folder category, Guid? parentID, GalleryCategory newCategory) { object result = null; try { // find the gallery category in the database (by ID if present, otherwise by name) GalleryCategory galleryCategory = newCategory.ID > 0 ? suggestionsContext.GalleryCategories.FirstOrDefault(gc => gc.ID == newCategory.ID) : suggestionsContext.GalleryCategories.FirstOrDefault(gc => gc.Name == newCategory.Name); if (galleryCategory == null) { TraceLog.TraceError("InstallCategory could not find Category ID " + newCategory.ID); return result; } // if the caller didn't pass in a Category, merge with an existing folder (by name), or create a new folder if (category == null) { category = CreateCategory(userContext, user, newCategory.Name); parentID = null; result = category; } else { // otherwise, create this (sub)category as a subitem DateTime now = DateTime.Now; var subCategory = new Item() { ID = Guid.NewGuid(), Name = galleryCategory.Name, FolderID = category.ID, ParentID = parentID, UserID = category.UserID, ItemTypeID = SystemItemTypes.Category, IsList = true, Created = now, LastModified = now, }; result = subCategory; // make this the last subcategory in the category float sortOrder = (userContext.Items.Any(i => i.UserID == category.UserID && i.FolderID == category.ID && i.ItemTypeID == SystemItemTypes.Category && (parentID.HasValue ? i.ParentID == parentID : i.ParentID == null)) ? userContext.Items.Where(i => i.UserID == category.UserID && i.FolderID == category.ID && i.ItemTypeID == SystemItemTypes.Category && (parentID.HasValue ? i.ParentID == parentID : i.ParentID == null)). Select(i => i.SortOrder). Max() : 0f) + 1000f; subCategory.SortOrder = sortOrder; userContext.Items.Add(subCategory); userContext.SaveChanges(); parentID = subCategory.ID; } // process all of the subcategories under this (sub)category in the database var subCategories = suggestionsContext.GalleryCategories.Where(gc => gc.ParentID == galleryCategory.ID); foreach (var sc in subCategories) InstallCategory(userContext, suggestionsContext, user, category, parentID, sc); // process all the Activities under this (sub)category var activities = suggestionsContext.GalleryActivities.Where(ga => ga.CategoryID == galleryCategory.ID); foreach (var activity in activities) InstallActivity(userContext, suggestionsContext, category, parentID, activity); } catch (Exception ex) { TraceLog.TraceException("InstallCategory failed", ex); return result; } return result; }
public StepProcessor(User user, UserStorageContext storage) { this.user = user; this.storage = storage; }
public ClientUserFolder(UserStorageContext storage) { this.storage = storage; }
public NextStepsDataModel(BaseResource resource) { this.storageContext = resource.StorageContext; this.currentUser = resource.CurrentUser; }
public bool GetUserInfo(User user, UserStorageContext storage) { // store user information from Facebook in UserProfile UserProfile userProfile = storage.ClientFolder.GetUserProfile(user); if (userProfile == null) { TraceLog.TraceError("Could not access UserProfile to import Google information into."); return false; } try { // import information about the current user var settings = this.CalendarSettings; if (userProfile.Timezone == null) { var olsonTZ = settings.Items.FirstOrDefault(i => i.Id == "timezone").Value; var tzinfo = OlsonTimeZoneToTimeZoneInfo(olsonTZ); userProfile.Timezone = tzinfo.Id; userProfile.TimezoneHoursOffset = tzinfo.BaseUtcOffset.Hours.ToString(); } storage.SaveChanges(); TraceLog.TraceInfo("Imported Google information into UserProfile"); } catch (Exception ex) { TraceLog.TraceException("Google query for basic User information failed", ex); return false; } return true; }
public ServerUserFolder(UserStorageContext storage) { this.storage = storage; }
private static string NextStepsList(User user, UserStorageContext userContext) { // get steps with duedates var steps = userContext.Items.Include("FieldValues").Where(i => i.UserID == user.ID && i.ItemTypeID == SystemItemTypes.Step && i.Status == StatusTypes.Active && i.FieldValues.Any(fv => fv.FieldName == FieldNames.DueDate)). ToList(); // create an ordered list of steps that have a duedate on or before today var stepList = new List<StepInfo>(); DateTime date; foreach (var s in steps) if (DateTime.TryParse(s.GetFieldValue(FieldNames.DueDate).Value, out date) && date.Date <= DateTime.Today) stepList.Add(new StepInfo() { Name = s.Name, Date = date }); var orderedSteps = stepList.OrderBy(s => s.Date); // build a string that contains a step in "date: name" format for each step var sb = new StringBuilder(); foreach (var s in orderedSteps) sb.AppendFormat("{0}: {1}\n", s.Date.ToString("MMM dd"), s.Name); var stepString = sb.ToString(); return stepString; }
public NextStepsDataModel(User user, UserStorageContext storage) { this.storageContext = storage; this.currentUser = user; }