Exemple #1
0
        public async Task InitAsync(UserSecret currentUserSecret, SFProjectSecret projectSecret, List <User> ptUsers,
                                    string paratextProjectId)
        {
            _currentUserSecret       = currentUserSecret;
            _currentParatextUsername = _paratextService.GetParatextUsername(currentUserSecret);
            _projectSecret           = projectSecret;
            _idToSyncUser.Clear();
            _usernameToSyncUser.Clear();
            foreach (SyncUser syncUser in projectSecret.SyncUsers)
            {
                _idToSyncUser[syncUser.Id] = syncUser;
                _usernameToSyncUser[syncUser.ParatextUsername] = syncUser;
            }
            _ptProjectUsersWhoCanWriteNotes = new HashSet <string>();
            IReadOnlyDictionary <string, string> roles = await _paratextService.GetProjectRolesAsync(currentUserSecret,
                                                                                                     paratextProjectId);

            var ptRolesCanWriteNote = new HashSet <string> {
                SFProjectRole.Administrator, SFProjectRole.Translator,
                SFProjectRole.Consultant, SFProjectRole.WriteNote
            };

            foreach (User user in ptUsers)
            {
                // Populate the list with all Paratext users belonging to the project and who can write notes
                if (roles.TryGetValue(user.ParatextId, out string role) && ptRolesCanWriteNote.Contains(role))
                {
                    _ptProjectUsersWhoCanWriteNotes.Add(user.Id);
                }
            }
        }
Exemple #2
0
        public async Task <ActionResult <string> > UsernameAsync()
        {
            Attempt <UserSecret> attempt = await _userSecrets.TryGetAsync(_userAccessor.UserId);

            if (!attempt.TryResult(out UserSecret userSecret))
            {
                return(NoContent());
            }
            string username = _paratextService.GetParatextUsername(userSecret);

            return(Ok(username));
        }
        public async Task <ActionResult <string> > UsernameAsync()
        {
            UserEntity user = await _users.GetAsync(_userAccessor.UserId);

            if (user.ParatextTokens == null)
            {
                return(NoContent());
            }
            string username = _paratextService.GetParatextUsername(user);

            return(Ok(username));
        }
        /// <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);
        }
        /// <summary>
        /// Iterates through SF projects on the server and identifies one administrator user on the project.
        /// Using the administrator user's secrets, perform a send/receive with the Paratext server. Effectively,
        /// this clones the project to the Scripture Forge server.
        /// This will overwrite any un-synchronized data on SF.
        /// </summary>
        public async Task CloneSFProjects(string mode, IEnumerable <SFProject> projectsToClone)
        {
            string syncDir = Path.Combine(_siteOptions.Value.SiteDir, "sync");
            bool   doClone = mode == CLONE || mode == CLONE_AND_MOVE_OLD || mode == CLONE_SILENT;

            string syncDirOld = Path.Combine(_siteOptions.Value.SiteDir, "sync_old");

            if (mode == CLONE_AND_MOVE_OLD)
            {
                if (!_fileSystemService.DirectoryExists(syncDirOld))
                {
                    _fileSystemService.CreateDirectory(syncDirOld);
                }
            }

            IConnection connection = await _realtimeService.ConnectAsync();

            // Get the paratext project ID and admin user for all SF Projects
            foreach (SFProject proj in projectsToClone)
            {
                bool foundAdmin = false;
                foreach (string userId in proj.UserRoles.Keys)
                {
                    if (proj.UserRoles.TryGetValue(userId, out string role) && role == SFProjectRole.Administrator)
                    {
                        foundAdmin = true;
                        UserSecret userSecret = _userSecretRepo.Query().FirstOrDefault((UserSecret us) => us.Id == userId);
                        string     ptUsername = _paratextService.GetParatextUsername(userSecret);
                        Log($"Project administrator identified on {proj.Name}: {ptUsername} ({userId})");
                        if (!doClone)
                        {
                            break;
                        }
                        try
                        {
                            var projectDoc = await connection.FetchAsync <SFProject>(proj.Id);

                            await projectDoc.SubmitJson0OpAsync(op =>
                            {
                                // Increment the queued count such as in SyncService
                                op.Inc(pd => pd.Sync.QueuedCount);
                            });

                            bool silent = mode == CLONE_SILENT;
                            // Clone the paratext project and update the SF database with the project data
                            await CloneAndSyncFromParatext(proj, userId, syncDir, silent);

                            if (mode == CLONE_AND_MOVE_OLD)
                            {
                                string projectDir    = Path.Combine(syncDir, proj.Id);
                                string projectDirOld = Path.Combine(syncDirOld, proj.Id);
                                _fileSystemService.MoveDirectory(projectDir, projectDirOld);
                            }
                            break;
                        }
                        catch (Exception e)
                        {
                            Log($"Unable to clone {proj.Name} ({proj.Id}) as user: {userId}{Environment.NewLine}" +
                                $"Error was: {e}");
                        }
                    }
                }
                if (!foundAdmin)
                {
                    Log($"ERROR: Unable to identify a project administrator on {proj.Name}");
                }
            }
        }