public static async Task <Attempt <T> > TryGetSnapshotAsync <T>(this IRealtimeService realtimeService, string id)
            where T : IIdentifiable
        {
            T entity = await realtimeService.QuerySnapshots <T>().FirstOrDefaultAsync(e => e.Id == id);

            return(new Attempt <T>(entity != null, entity));
        }
Example #2
0
        /// <summary> Selects the SF projects to clone and clones the projects. </summary>
        public static async Task SelectProjectsAndClone(IWebHost webHost, string mode)
        {
            string           cloneSubset   = Environment.GetEnvironmentVariable("CLONE_SET");
            HashSet <string> projectSubset = null;

            try
            {
                if (cloneSubset != null)
                {
                    projectSubset = new HashSet <string>(cloneSubset.Split(' '));
                }
            }
            catch
            {
                Log($"There was a problem parsing the CLONE_SET SF project ids "
                    + $"environment variable. Rethrowing.");
                throw;
            }
            IRealtimeService realtimeService = webHost.Services.GetService <IRealtimeService>();
            List <SFProject> projectsToClone = realtimeService.QuerySnapshots <SFProject>().ToList <SFProject>();

            if (projectSubset != null)
            {
                projectsToClone.RemoveAll((SFProject sfProject) => !projectSubset.Contains(sfProject.Id));
                string ids   = string.Join(' ', projectsToClone.Select((SFProject sfProject) => sfProject.Id));
                int    count = projectsToClone.Count;
                Log($"Only working on the subset of projects (count {count}) with these SF project ids: {ids}");
            }

            ICloneAllService cloneTool = webHost.Services.GetService <ICloneAllService>();
            await cloneTool.CloneSFProjects(mode, projectsToClone);
        }
Example #3
0
        private IReadOnlyList <ParatextProject> GetProjects(UserSecret userSecret,
                                                            IEnumerable <SharedRepository> remotePtProjects, IEnumerable <ProjectMetadata> projectsMetadata)
        {
            if (userSecret == null)
            {
                throw new ArgumentNullException();
            }

            List <ParatextProject> paratextProjects   = new List <ParatextProject>();
            IQueryable <SFProject> existingSfProjects = _realtimeService.QuerySnapshots <SFProject>();

            foreach (SharedRepository remotePtProject in remotePtProjects)
            {
                SFProject correspondingSfProject =
                    existingSfProjects.FirstOrDefault(sfProj => sfProj.ParatextId == remotePtProject.SendReceiveId.Id);

                bool sfProjectExists     = correspondingSfProject != null;
                bool sfUserIsOnSfProject = correspondingSfProject?.UserRoles.ContainsKey(userSecret.Id) ?? false;
                bool adminOnPtProject    = remotePtProject.SourceUsers.GetRole(
                    GetParatextUsername(userSecret)) == UserRoles.Administrator;
                bool ptProjectIsConnectable =
                    (sfProjectExists && !sfUserIsOnSfProject) || (!sfProjectExists && adminOnPtProject);

                // On SF Live server, many users have projects without corresponding project metadata.
                // If this happens, default to using the project's short name
                var projectMD = projectsMetadata
                                .SingleOrDefault(pmd => pmd.ProjectGuid == remotePtProject.SendReceiveId);
                string fullOrShortName = projectMD == null ? remotePtProject.ScrTextName : projectMD.FullName;

                paratextProjects.Add(new ParatextProject
                {
                    ParatextId    = remotePtProject.SendReceiveId.Id,
                    Name          = fullOrShortName,
                    ShortName     = remotePtProject.ScrTextName,
                    LanguageTag   = correspondingSfProject?.WritingSystem.Tag,
                    ProjectId     = correspondingSfProject?.Id,
                    IsConnectable = ptProjectIsConnectable,
                    IsConnected   = sfProjectExists && sfUserIsOnSfProject
                });
            }
            return(paratextProjects.OrderBy(project => project.Name, StringComparer.InvariantCulture).ToArray());
        }
Example #4
0
        /// <summary>
        /// First-stage migrator. Synchronize all SF projects to the Paratext Data Access server.
        /// First query information that will show whether we should be able to sync all projects. In addition to
        /// reporting information on projects and whether there is an admin that can sync the project, this method shows
        /// that the admin can successfully perform queries to both the PT Registry and the PT Data Access web APIs, via
        /// various ParatextService method calls.
        /// If `doSynchronizations` is false, only do the above reporting. If true, also synchronize the SF DB with the
        /// Paratext Data Access server.
        /// </summary>
        public async Task SynchronizeAllProjectsAsync(bool doSynchronizations,
                                                      ISet <string> sfProjectIdsToSynchronize = null, IDictionary <string, string> sfAdminsToUse = null)
        {
            List <SFProject> allSfProjects = _realtimeService.QuerySnapshots <SFProject>().ToList <SFProject>();

            if (sfProjectIdsToSynchronize != null)
            {
                allSfProjects.RemoveAll((SFProject sfProject) => !sfProjectIdsToSynchronize.Contains(sfProject.Id));
                string ids   = string.Join(' ', allSfProjects.Select((SFProject sfProject) => sfProject.Id));
                int    count = allSfProjects.Count;
                _logger.Log($"Only working on the subset of projects (count {count}) with these SF project ids: {ids}");
            }
            _realtimeServiceConnection = await _realtimeService.ConnectAsync();

            List <Task> syncTasks = new List <Task>();

            // Report on all SF projects.
            foreach (SFProject sfProject in allSfProjects)
            {
                _logger.Log($"{Program.Bullet1} PT project {sfProject.ShortName}, "
                            + $"PT project id {sfProject.ParatextId}, SF project id {sfProject.Id}.");
                List <string> projectSfAdminUserIds = sfProject.UserRoles
                                                      .Where(ur => ur.Value == SFProjectRole.Administrator).Select(ur => ur.Key).ToList <string>();
                if (projectSfAdminUserIds.Count() < 1)
                {
                    List <string> projectSfUserIds = sfProject.UserRoles.Select(ur => ur.Key).ToList <string>();
                    string        users            = string.Join(", ", projectSfUserIds);
                    if (projectSfUserIds.Count() < 1)
                    {
                        users = "None";
                    }
                    _logger.Log($"  {Program.Bullet2} Warning: no admin users. Non-admin users include: {users}");
                }

                // Report on all admins in a project
                foreach (string sfUserId in projectSfAdminUserIds)
                {
                    UserSecret userSecret = _userSecretRepo.Query()
                                            .FirstOrDefault((UserSecret us) => us.Id == sfUserId);
                    string ptUsername = null;
                    string ptUserId   = null;
                    try
                    {
                        ptUsername = _paratextService.GetParatextUsername(userSecret);
                        ptUserId   = GetParatextUserId(userSecret);
                    }
                    catch (Exception e)
                    {
                        _logger.Log($"  {Program.Bullet2} Failure getting SF user's PT username or PT user id. " +
                                    $"Skipping. SF user id was {sfUserId}. If known, PT username was {ptUsername}. " +
                                    $"Error with stack was {e}");
                        continue;
                    }
                    _logger.Log($"  {Program.Bullet2} PT user '{ptUsername}', "
                                + $"id {ptUserId}, using SF admin user id {sfUserId} on SF project.");

                    string rt  = $"{userSecret.ParatextTokens.RefreshToken.Substring(0, 5)}..";
                    string at  = $"{userSecret.ParatextTokens.AccessToken.Substring(0, 5)}..";
                    bool   atv = userSecret.ParatextTokens.ValidateLifetime();
                    _logger.Log($"    {Program.Bullet3} Paratext RefreshToken: {rt}, "
                                + $"AccessToken: {at}, AccessToken initially valid: {atv}.");

                    // Demonstrate access to PT Registry, and report Registry's statement of role.
                    _logger.Log($"    {Program.Bullet3} PT Registry report on role on PT project: ", false);
                    IReadOnlyDictionary <string, string> ptProjectRoles = null;
                    try
                    {
                        ptProjectRoles = await _paratextService.GetProjectRolesAsync(userSecret, sfProject.ParatextId);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine($"      Failure fetching user's PT project roles. Skipping. " +
                                          $"Error was {e.Message}");
                        continue;
                    }
                    if (ptProjectRoles.TryGetValue(ptUserId, out string ptRole))
                    {
                        Console.WriteLine($"{ptRole}");
                    }
                    else
                    {
                        Console.WriteLine($"Not found.");
                    }
                    // Demonstrate access to PT Data Access.
                    IReadOnlyList <ParatextProject> userPtProjects = null;
                    try
                    {
                        userPtProjects = await _paratextService.GetProjectsAsync(userSecret);
                    }
                    catch (Exception e)
                    {
                        _logger.Log($"    {Program.Bullet3} Failure fetching user's PT projects. Skipping. "
                                    + $"Error was {e.Message}");
                        continue;
                    }

                    _logger.Log($"    {Program.Bullet3} PT Data Access and PT Registry "
                                + "based report on projects the user can access, narrowed to this project: ", false);
                    List <string> ptProjectNamesList = userPtProjects
                                                       .Where(ptProject => ptProject.ParatextId == sfProject.ParatextId)
                                                       .Select(ptProject => ptProject.ShortName).ToList();
                    string ptProjectNames = string.Join(',', ptProjectNamesList);
                    if (ptProjectNamesList.Count() < 1)
                    {
                        ptProjectNames = $"User is not on this project. " +
                                         $"PT reports they are on this many PT projects: {userPtProjects.Count()}";
                    }
                    Console.WriteLine(ptProjectNames);

                    if (doSynchronizations)
                    {
                        if (sfAdminsToUse != null && sfAdminsToUse.ContainsKey(sfProject.Id))
                        {
                            sfAdminsToUse.TryGetValue(sfProject.Id, out string sfAdminIdToUse);
                            bool isUserAtHand = sfUserId == sfAdminIdToUse;
                            if (isUserAtHand)
                            {
                                _logger.Log($"  {Program.Bullet2} For SF Project {sfProject.Id}, we were asked to use "
                                            + $"this SF user {sfUserId} to sync.");
                            }
                            else
                            {
                                _logger.Log($"  {Program.Bullet2} For SF Project {sfProject.Id}, we were asked to use "
                                            + $"SF user {sfAdminIdToUse}, not {sfUserId}, to sync. So skipping this user.");
                                continue;
                            }
                        }

                        try
                        {
                            _logger.Log($"  {Program.Bullet2} Starting an asynchronous synchronization for "
                                        + $"SF project {sfProject.Id} as SF user {sfUserId}.");
                            Task syncTask   = SynchronizeProject(sfUserId, sfProject.Id);
                            var  projectDoc = await _realtimeServiceConnection.FetchAsync <SFProject>(sfProject.Id);

                            // Increment the queued count (such as done in SyncService), since it gets decremented
                            // later by ParatextSyncRunner.
                            await projectDoc.SubmitJson0OpAsync(op => op.Inc(pd => pd.Sync.QueuedCount));

                            _logger.Log($"    {Program.Bullet3} Synchronization task for SF project {sfProject.Id} as "
                                        + $"SF user {sfUserId} has Sync Task Id {syncTask.Id}.");
                            syncTasks.Add(syncTask);
                            break;
                        }
                        catch (Exception e)
                        {
                            // We probably won't get here. But just in case.
                            _logger.Log($"    {Program.Bullet3} There was a problem with synchronizing. It might be "
                                        + $"tried next with another admin user. Exception is:{Environment.NewLine}{e}");
                            continue;
                        }
                    }
                }
            }

            if (doSynchronizations)
            {
                _logger.Log("Waiting for synchronization tasks to finish (if any). "
                            + $"There are this many tasks: {syncTasks.Count}");
                try
                {
                    Task allTasks = Task.WhenAll(syncTasks);
                    try
                    {
                        await allTasks.ConfigureAwait(false);
                    }
                    catch
                    {
                        if (allTasks.Exception == null)
                        {
                            throw;
                        }
                        ExceptionDispatchInfo.Capture(allTasks.Exception).Throw();
                    }
                    _logger.Log("Synchronization tasks are finished.");
                }
                catch (AggregateException e)
                {
                    _logger.Log("There was a problem with one or more synchronization tasks. "
                                + $"Exception is:{Environment.NewLine}{e}");
                }

                if (syncTasks.Any(task => !task.IsCompletedSuccessfully))
                {
                    _logger.Log("One or more sync tasks did not complete successfully.");
                }
                else
                {
                    _logger.Log("All sync tasks finished with a claimed Task status of Completed Successfully.");
                }

                _logger.Log($"{Program.Bullet1} Sync task completion results:");
                foreach (Task task in syncTasks)
                {
                    string exceptionInfo = $"with exception {task.Exception?.InnerException}.";
                    if (task.Exception == null)
                    {
                        exceptionInfo = "with no unhandled exception thrown.";
                    }
                    _logger.Log($"  {Program.Bullet2} Sync task Id {task.Id} has status {task.Status} {exceptionInfo}");
                    if (task.Exception?.InnerExceptions?.Count > 1)
                    {
                        _logger.Log($"    {Program.Bullet3} Sync task Id {task.Id} has more than one inner exception. "
                                    + "Sorry if this is redundant, but they are:");
                        foreach (var e in task.Exception.InnerExceptions)
                        {
                            _logger.Log($"    {Program.Bullet3} Inner exception: {e}");
                        }
                    }
                }
            }

            ReportLastSyncSuccesses(allSfProjects);
        }