/// <summary> /// Thread that is waiting for signals to perform checks. /// </summary> private void ExecutiveThread() { log.Info("()"); executiveThreadFinished.Reset(); List <WaitHandle> handleList = new List <WaitHandle>(); handleList.Add(ShutdownSignaling.ShutdownEvent); foreach (CronJob job in jobs.Values) { handleList.Add(job.Event); } WaitHandle[] handles = handleList.ToArray(); Data.Database database = (Data.Database)Base.ComponentDictionary["Data.Database"]; Network.LocationBasedNetwork locationBasedNetwork = (Network.LocationBasedNetwork)Base.ComponentDictionary["Network.LocationBasedNetwork"]; ImageManager imageManager = (ImageManager)Base.ComponentDictionary["Data.ImageManager"]; Network.Server server = (Network.Server)Base.ComponentDictionary["Network.Server"]; Network.NeighborhoodActionProcessor neighborhoodActionProcessor = (Network.NeighborhoodActionProcessor)Base.ComponentDictionary["Network.NeighborhoodActionProcessor"]; Network.CAN.ContentAddressNetwork contentAddressNetwork = (Network.CAN.ContentAddressNetwork)Base.ComponentDictionary["Network.ContentAddressNetwork"]; while (!ShutdownSignaling.IsShutdown) { log.Info("Waiting for event."); int index = WaitHandle.WaitAny(handles); if (handles[index] == ShutdownSignaling.ShutdownEvent) { log.Info("Shutdown detected."); break; } CronJob job = null; foreach (CronJob cronJob in jobs.Values) { if (handles[index] == cronJob.Event) { job = cronJob; break; } } log.Trace("Job '{0}' activated.", job.Name); switch (job.Name) { case "checkFollowersRefresh": database.CheckFollowersRefresh(); break; case "checkExpiredHostedIdentities": database.CheckExpiredHostedIdentities(); break; case "checkExpiredNeighbors": if (locationBasedNetwork.LocServerInitialized) { database.CheckExpiredNeighbors(); } else { log.Debug("LOC component is not in sync with the LOC server yet, checking expired neighbors will not be executed now."); } break; case "deleteUnusedImages": imageManager.ProcessImageDeleteList(); break; case "refreshLocData": locationBasedNetwork.RefreshLoc(); break; case "checkInactiveClientConnections": server.CheckInactiveClientConnections(); break; case "checkNeighborhoodActionList": neighborhoodActionProcessor.CheckActionList(); break; case "ipnsRecordRefresh": contentAddressNetwork.IpnsRecordRefresh().Wait(); break; } } executiveThreadFinished.Set(); log.Info("(-)"); }
/// <summary> /// Updates identity profile and creates neighborhood action to propagate the change unless the client did not want the propagation. /// </summary> /// <param name="IdentityId">Network identifier of the identity to update.</param> /// <param name="SignedProfile">Signed profile information with updated values.</param> /// <param name="ProfileImageChanged">True if profile image is about to change.</param> /// <param name="ThumbnailImageChanged">True if thumbnail image is about to change.</param> /// <param name="NoPropagation">True if the client does not want this change of profile to be propagated to the neighborhood.</param> /// <param name="IdentityNotFound">If the function fails because the identity is not found, this referenced value is set to true.</param> /// <param name="ImagesToDelete">If the function succeeds and the profile images are altered, old image files has to be deleted, in which case their hashes /// are returned in this list, which has to be initialized by the caller.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> UpdateProfileAndPropagateAsync(byte[] IdentityId, SignedProfileInformation SignedProfile, bool ProfileImageChanged, bool ThumbnailImageChanged, bool NoPropagation, StrongBox <bool> IdentityNotFound, List <byte[]> ImagesToDelete) { log.Trace("()"); bool res = false; bool signalNeighborhoodAction = false; bool success = false; List <byte[]> imagesToDelete = new List <byte[]>(); ProfileInformation profile = SignedProfile.Profile; DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.HostedIdentityLock, UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock }; using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects)) { try { HostedIdentity identity = (await GetAsync(i => (i.IdentityId == IdentityId) && (i.Cancelled == false))).FirstOrDefault(); if (identity != null) { bool isProfileInitialization = !identity.Initialized; identity.Initialized = true; identity.Version = profile.Version.ToByteArray(); identity.Name = profile.Name; GpsLocation location = new GpsLocation(profile.Latitude, profile.Longitude); identity.SetInitialLocation(location); identity.ExtraData = profile.ExtraData; identity.Signature = SignedProfile.Signature.ToByteArray(); if (ProfileImageChanged) { if (identity.ProfileImage != null) { imagesToDelete.Add(identity.ProfileImage); } identity.ProfileImage = profile.ProfileImageHash.Length != 0 ? profile.ProfileImageHash.ToByteArray() : null; } if (ThumbnailImageChanged) { if (identity.ThumbnailImage != null) { imagesToDelete.Add(identity.ThumbnailImage); } identity.ThumbnailImage = profile.ThumbnailImageHash.Length != 0 ? profile.ThumbnailImageHash.ToByteArray() : null; } Update(identity); if (!NoPropagation) { // The profile change has to be propagated to all our followers // we create database actions that will be processed by dedicated thread. NeighborhoodActionType actionType = isProfileInitialization ? NeighborhoodActionType.AddProfile : NeighborhoodActionType.ChangeProfile; string extraInfo = identity.PublicKey.ToHex(); signalNeighborhoodAction = await unitOfWork.NeighborhoodActionRepository.AddIdentityProfileFollowerActionsAsync(actionType, identity.IdentityId, extraInfo); } await unitOfWork.SaveThrowAsync(); transaction.Commit(); success = true; } else { IdentityNotFound.Value = true; } } catch (Exception e) { log.Error("Exception occurred: {0}", e.ToString()); } if (!success) { log.Warn("Rolling back transaction."); unitOfWork.SafeTransactionRollback(transaction); } unitOfWork.ReleaseLock(lockObjects); } if (success) { // Only when the function succeeds the old images can be deleted. ImagesToDelete.AddRange(imagesToDelete); // Send signal to neighborhood action processor to process the new series of actions. if (signalNeighborhoodAction) { Network.NeighborhoodActionProcessor neighborhoodActionProcessor = (Network.NeighborhoodActionProcessor)Base.ComponentDictionary[Network.NeighborhoodActionProcessor.ComponentName]; neighborhoodActionProcessor.Signal(); } res = true; } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Deletes an existing activity from the database. Then a new neighborhood action is created to propagate the change to the neighborhood. /// </summary> /// <param name="ActivityId">Identifier of the activity to delete.</param> /// <param name="OwnerIdentityId">Network ID of the client who owns the activity.</param> /// <param name="NotFound">If the function fails, this is set to true if the reason of the failure is that the activity was not found.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> DeleteAndPropagateAsync(uint ActivityId, byte[] OwnerIdentityId, StrongBox <bool> NotFound) { log.Trace("(ActivityId:{0},OwnerIdentityId:'{1}')", ActivityId, OwnerIdentityId.ToHex()); bool res = false; bool success = false; bool signalNeighborhoodAction = false; DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.PrimaryActivityLock, UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock }; using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects)) { try { PrimaryActivity existingActivity = (await GetAsync(a => (a.ActivityId == ActivityId) && (a.OwnerIdentityId == OwnerIdentityId))).FirstOrDefault(); if (existingActivity != null) { Delete(existingActivity); // The activity has to be propagated to all our followers we create database actions that will be processed by dedicated thread. signalNeighborhoodAction = await unitOfWork.NeighborhoodActionRepository.AddActivityFollowerActionsAsync(NeighborhoodActionType.RemoveActivity, existingActivity.ActivityId, existingActivity.OwnerIdentityId); await unitOfWork.SaveThrowAsync(); transaction.Commit(); success = true; } else { log.Debug("Activity ID {0}, owner identity ID '{1}' does not exist.", ActivityId, OwnerIdentityId.ToHex()); NotFound.Value = true; } } catch (Exception e) { log.Error("Exception occurred: {0}", e.ToString()); } if (!success) { log.Warn("Rolling back transaction."); unitOfWork.SafeTransactionRollback(transaction); } unitOfWork.ReleaseLock(lockObjects); } if (success) { // Send signal to neighborhood action processor to process the new series of actions. if (signalNeighborhoodAction) { Network.NeighborhoodActionProcessor neighborhoodActionProcessor = (Network.NeighborhoodActionProcessor)Base.ComponentDictionary[Network.NeighborhoodActionProcessor.ComponentName]; neighborhoodActionProcessor.Signal(); } res = true; } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Inserts a new activity to the database provided that it does not exists yet. Then a new neighborhood action is created to propagate the new activity to the neighborhood. /// </summary> /// <param name="Activity">New activity to insert to database.</param> /// <returns>Status.Ok if the function succeeds, /// Status.ErrorAlreadyExists if activity with the same owner and ID already exists, /// Status.ErrorQuotaExceeded if the server is not willing to create any more activities because it has reached its limit already, /// Status.ErrorInternal if the function fails for other reason.</returns> public async Task <Status> CreateAndPropagateAsync(PrimaryActivity Activity) { log.Trace("()"); Status res = Status.ErrorInternal; bool success = false; bool signalNeighborhoodAction = false; DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.PrimaryActivityLock, UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock }; using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects)) { try { int hostedActivities = await CountAsync(null); log.Trace("Currently hosting {0} activities.", hostedActivities); if (hostedActivities < Config.Configuration.MaxActivities) { PrimaryActivity existingActivity = (await GetAsync(a => (a.ActivityId == Activity.ActivityId) && (a.OwnerIdentityId == Activity.OwnerIdentityId))).FirstOrDefault(); if (existingActivity == null) { await InsertAsync(Activity); // The activity has to be propagated to all our followers we create database actions that will be processed by dedicated thread. signalNeighborhoodAction = await unitOfWork.NeighborhoodActionRepository.AddActivityFollowerActionsAsync(NeighborhoodActionType.AddActivity, Activity.ActivityId, Activity.OwnerIdentityId, Activity.OwnerPublicKey.ToHex()); await unitOfWork.SaveThrowAsync(); transaction.Commit(); success = true; } else { log.Debug("Activity with the activity ID {0} and owner identity ID '{1}' already exists with database ID {2}.", Activity.ActivityId, Activity.OwnerIdentityId.ToHex(), existingActivity.DbId); res = Status.ErrorAlreadyExists; } } else { log.Debug("MaxActivities {0} has been reached.", Config.Configuration.MaxActivities); res = Status.ErrorQuotaExceeded; } } catch (Exception e) { log.Error("Exception occurred: {0}", e.ToString()); } if (!success) { log.Warn("Rolling back transaction."); unitOfWork.SafeTransactionRollback(transaction); } unitOfWork.ReleaseLock(lockObjects); } if (success) { // Send signal to neighborhood action processor to process the new series of actions. if (signalNeighborhoodAction) { Network.NeighborhoodActionProcessor neighborhoodActionProcessor = (Network.NeighborhoodActionProcessor)Base.ComponentDictionary[Network.NeighborhoodActionProcessor.ComponentName]; neighborhoodActionProcessor.Signal(); } res = Status.Ok; } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Updates an existing activity in the database. Then a new neighborhood action is created to propagate the changes to the neighborhood /// if this is required. If the update is rejected, the action is deleted from the database and this is propagated to the neighborhood. /// <para>Note that the change in activity is not propagated if the client sets no propagation flag in the request, or if only the activity /// expiration date or its location is changed.</para> /// </summary> /// <param name="UpdateRequest">Update request received from the client.</param> /// <param name="Signature">Signature of the updated activity data from the client.</param> /// <param name="OwnerIdentityId">Network ID of the client who requested the update.</param> /// <param name="CloserServerId">If the result is Status.ErrorRejected, this is filled with network identifier of a neighbor server that is closer to the target location.</param> /// <returns>Status.Ok if the function succeeds, /// Status.ErrorNotFound if the activity to update does not exist, /// Status.ErrorRejected if the update was rejected and the client should migrate the activity to closest proximity server, /// Status.ErrorInvalidValue if the update attempted to change activity's type, /// Status.ErrorInternal otherwise.</returns> public async Task <Status> UpdateAndPropagateAsync(UpdateActivityRequest UpdateRequest, byte[] Signature, byte[] OwnerIdentityId, StrongBox <byte[]> CloserServerId) { log.Trace("(UpdateRequest.Activity.Id:{0},OwnerIdentityId:'{1}')", UpdateRequest.Activity.Id, OwnerIdentityId.ToHex()); Status res = Status.ErrorInternal; bool success = false; bool signalNeighborhoodAction = false; bool migrateActivity = false; ActivityInformation activityInformation = UpdateRequest.Activity; DatabaseLock[] lockObjects = new DatabaseLock[] { UnitOfWork.PrimaryActivityLock, UnitOfWork.FollowerLock, UnitOfWork.NeighborhoodActionLock }; using (IDbContextTransaction transaction = await unitOfWork.BeginTransactionWithLockAsync(lockObjects)) { try { PrimaryActivity existingActivity = (await GetAsync(a => (a.ActivityId == activityInformation.Id) && (a.OwnerIdentityId == OwnerIdentityId))).FirstOrDefault(); if (existingActivity != null) { // First, we check whether the activity should be migrated to closer proximity server. GpsLocation oldLocation = existingActivity.GetLocation(); GpsLocation newLocation = new GpsLocation(activityInformation.Latitude, activityInformation.Longitude); bool locationChanged = !oldLocation.Equals(newLocation); bool error = false; if (locationChanged) { List <byte[]> ignoreServerIds = new List <byte[]>(UpdateRequest.IgnoreServerIds.Select(i => i.ToByteArray())); if (!await unitOfWork.NeighborRepository.IsServerNearestToLocationAsync(newLocation, ignoreServerIds, CloserServerId, ProxMessageBuilder.ActivityMigrationDistanceTolerance)) { if (CloserServerId.Value != null) { migrateActivity = true; log.Debug("Activity's new location is outside the reach of this proximity server, the activity will be deleted from the database."); } else { error = true; } } // else No migration needed } if (!error) { // If it should not be migrated, we update the activity in our database. if (!migrateActivity) { SignedActivityInformation signedActivityInformation = new SignedActivityInformation() { Activity = activityInformation, Signature = ProtocolHelper.ByteArrayToByteString(Signature) }; PrimaryActivity updatedActivity = ActivityBase.FromSignedActivityInformation <PrimaryActivity>(signedActivityInformation); ActivityChange changes = existingActivity.CompareChangeTo(updatedActivity); if ((changes & ActivityChange.Type) == 0) { bool propagateChange = false; if (!UpdateRequest.NoPropagation) { // If only changes in the activity are related to location or expiration time, the activity update is not propagated to the neighborhood. propagateChange = (changes & ~(ActivityChange.LocationLatitude | ActivityChange.LocationLongitude | ActivityChange.PrecisionRadius | ActivityChange.ExpirationTime)) != 0; } existingActivity.CopyFromSignedActivityInformation(signedActivityInformation); Update(existingActivity); if (propagateChange) { // The activity has to be propagated to all our followers we create database actions that will be processed by dedicated thread. signalNeighborhoodAction = await unitOfWork.NeighborhoodActionRepository.AddActivityFollowerActionsAsync(NeighborhoodActionType.ChangeActivity, existingActivity.ActivityId, existingActivity.OwnerIdentityId); } else { log.Trace("Change of activity ID {0}, owner identity ID '{1}' won't be propagated to neighborhood.", existingActivity.ActivityId, existingActivity.OwnerIdentityId.ToHex()); } await unitOfWork.SaveThrowAsync(); transaction.Commit(); success = true; } else { log.Debug("Activity ID {0}, owner identity ID '{1}' attempt to change type.", activityInformation.Id, OwnerIdentityId.ToHex()); res = Status.ErrorInvalidValue; } } // else this is handled below separately, out of locked section } // else Internal error } else { log.Debug("Activity ID {0}, owner identity ID '{1}' does not exist.", activityInformation.Id, OwnerIdentityId.ToHex()); res = Status.ErrorNotFound; } } catch (Exception e) { log.Error("Exception occurred: {0}", e.ToString()); } if (!success) { log.Warn("Rolling back transaction."); unitOfWork.SafeTransactionRollback(transaction); } unitOfWork.ReleaseLock(lockObjects); } if (migrateActivity) { // If activity should be migrated, we delete it from our database and inform our neighbors. StrongBox <bool> notFound = new StrongBox <bool>(false); if (await DeleteAndPropagateAsync(activityInformation.Id, OwnerIdentityId, notFound)) { if (!notFound.Value) { // The activity was deleted from the database and this change will be propagated to the neighborhood. log.Debug("Update rejected, activity ID {0}, owner identity ID '{1}' deleted.", activityInformation.Id, OwnerIdentityId.ToHex()); res = Status.ErrorRejected; } else { // Activity of given ID not found among activities created by the client. res = Status.ErrorNotFound; } } } if (success) { // Send signal to neighborhood action processor to process the new series of actions. if (signalNeighborhoodAction) { Network.NeighborhoodActionProcessor neighborhoodActionProcessor = (Network.NeighborhoodActionProcessor)Base.ComponentDictionary[Network.NeighborhoodActionProcessor.ComponentName]; neighborhoodActionProcessor.Signal(); } res = Status.Ok; } log.Trace("(-):{0}", res); return(res); }