Beispiel #1
0
        /// <summary>
        /// Clone Paratext project data into the SF projects sync folder. Then synchronize existing books
        /// and notes in project.
        /// </summary>
        public async Task CloneAndSyncFromParatext(SFProject proj, string userId, string syncDir, bool silent)
        {
            Log($"Cloning {proj.Name} ({proj.Id}) as SF user {userId}");
            string existingCloneDir = Path.Combine(syncDir, proj.ParatextId);

            // If the project directory already exists, no need to sync the project
            if (_fileSystemService.DirectoryExists(existingCloneDir))
            {
                Log("The project has already been cloned. Skipping...");
                return;
            }
            try
            {
                await CloneProject(proj.Id, userId, silent);

                Log($"{proj.Name} - Succeeded");
                if (silent)
                {
                    Log($"Deleting cloned repository for {proj.Name}");
                    _fileSystemService.DeleteDirectory(existingCloneDir);
                }
            }
            catch (Exception e)
            {
                Log($"There was a problem cloning the project.{Environment.NewLine}Exception is: {e}");
                if (_fileSystemService.DirectoryExists(existingCloneDir))
                {
                    _fileSystemService.DeleteDirectory(existingCloneDir);
                }
                throw;
            }
        }
Beispiel #2
0
        public async Task <IEnumerable <SFProject> > GetSFProjects(string[] sfProjectIds)
        {
            List <SFProject> projectsResults = new List <SFProject>();

            foreach (string id in sfProjectIds)
            {
                SFProject proj = await RealtimeService.GetSnapshotAsync <SFProject>(id);

                projectsResults.Add(proj);
            }
            return(projectsResults);
        }
        public async Task InviteAsync_LinkSharingEnabled_UserInvited()
        {
            var       env     = new TestEnvironment();
            SFProject project = env.GetProject(Project02);

            Assert.That(project.CheckingConfig.ShareEnabled, Is.True, "setup");
            Assert.That(project.CheckingConfig.ShareLevel, Is.EqualTo(CheckingShareLevel.Anyone), "setup: link sharing should be enabled");
            const string email = "*****@*****.**";
            // SUT
            await env.Service.InviteAsync(User01, Project02, email);

            await env.EmailService.Received(1).SendEmailAsync(email, Arg.Any <string>(),
                                                              Arg.Is <string>(body => body.Contains($"http://localhost/projects/{Project02}?sharing=true&shareKey=1234abc")));
        }
Beispiel #4
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());
        }
Beispiel #5
0
        /// <summary>
        /// Creates an internal test project.
        /// </summary>
        /// <param name="sourceId">The source project/resource identifier.</param>
        /// <param name="targetId">The target project identifier.
        /// This is the project that will reference this project/resource a source.</param>
        /// <returns>THe task</returns>
        /// <exception cref="DataNotFoundException">
        /// The target project does not exist
        /// or
        /// The user does not exist.
        /// </exception>
        /// <remarks>
        /// This is only to be used on test runs!
        /// </remarks>
        internal async Task CreateInternalTestProjectAsync(string sourceId, string targetId)
        {
            if (!this._testProjectCollection.Any(p => p.ParatextId == sourceId) && !string.IsNullOrWhiteSpace(sourceId))
            {
                // Create the test project
                SFProject testProject = new SFProject
                {
                    Id         = ObjectId.GenerateNewId().ToString(),
                    ParatextId = sourceId,
                };
                this._testProjectCollection.Add(testProject);

                // Load the source project from the target project's source directory (it is not moved in test mode)
                ScrText?scrText = SourceScrTextCollection.FindById("admin", targetId);
                if (scrText != null)
                {
                    // Create the test text objects for all of the books
                    foreach (int bookNum in scrText.Settings.BooksPresentSet.SelectedBookNumbers)
                    {
                        string usfm     = scrText.GetText(bookNum);
                        string bookText = UsfmToUsx.ConvertToXmlString(scrText, bookNum, usfm, false);
                        var    usxDoc   = XDocument.Parse(bookText);
                        Dictionary <int, ChapterDelta> deltas =
                            this._deltaUsxMapper.ToChapterDeltas(usxDoc).ToDictionary(cd => cd.Number);
                        var chapters = new List <Chapter>();
                        foreach (KeyValuePair <int, ChapterDelta> kvp in deltas)
                        {
                            this._testTextDataIdCollection.Add(TextData.GetTextDocId(testProject.Id, bookNum, kvp.Key));
                        }
                    }
                }
                else
                {
                    Program.Log($"Test Mode Error Migrating TextData For {sourceId} - Could Not Load From Filesystem!");
                }

                // See that at least one user in the target project has permission to create the source project
                var targetProject =
                    this._realtimeService.QuerySnapshots <SFProject>().FirstOrDefault(p => p.ParatextId == targetId);
                if (targetProject == null)
                {
                    throw new DataNotFoundException("The target project does not exist");
                }

                // Get the highest ranked users for this project, that probably have source access
                string[] userIds = targetProject.UserRoles
                                   .Where(ur => ur.Value == SFProjectRole.Administrator || ur.Value == SFProjectRole.Translator)
                                   .OrderBy(ur => ur.Value)
                                   .Select(ur => ur.Key)
                                   .ToArray();

                bool someoneCanAccessSourceProject = false;
                foreach (string userId in userIds)
                {
                    Attempt <UserSecret> userSecretAttempt = await _userSecrets.TryGetAsync(userId);

                    if (!userSecretAttempt.TryResult(out UserSecret userSecret))
                    {
                        throw new DataNotFoundException("The user does not exist.");
                    }

                    // We check projects first, in case it is a project
                    IReadOnlyList <ParatextProject> ptProjects = await _paratextService.GetProjectsAsync(userSecret);

                    if (ptProjects.Any(p => p.ParatextId == sourceId))
                    {
                        someoneCanAccessSourceProject = true;
                        break;
                    }
                }

                if (!someoneCanAccessSourceProject)
                {
                    Program.Log($"Test Mode Error Creating {sourceId} - Nobody In The Target Project Has Access!");
                }
            }
        }
Beispiel #6
0
        /// <summary>
        /// Migrates the objects.
        /// </summary>
        /// <param name="doWrite">If set to <c>true</c>, do write changes to the database.</param>
        /// <returns>
        /// The task.
        /// </returns>
        public async Task MigrateObjectsAsync(bool doWrite)
        {
            // Get the existing projects from MongoDB
            List <SFProject> existingProjects = this._realtimeService.QuerySnapshots <SFProject>().ToList();

            // If we are testing, add the test projects
            if (!doWrite)
            {
                existingProjects.AddRange(this._testProjectCollection);
            }

            // This is the mapping for update the TextData identifiers
            // The key is the target project id, the value is the new source project id
            Dictionary <string, string> sourceMapping = new Dictionary <string, string>();

            // Connect to the realtime service
            using IConnection connection = await this._realtimeService.ConnectAsync();

            // Iterate over every project in database
            foreach (var project in existingProjects)
            {
                // Update ProjectRef value
                string?sourceParatextId = project.TranslateConfig?.Source?.ParatextId;
                if (!string.IsNullOrWhiteSpace(sourceParatextId))
                {
                    // Get the source project
                    SFProject sourceProject = existingProjects.FirstOrDefault(p => p.ParatextId == sourceParatextId);
                    if (sourceProject != null)
                    {
                        // Update the database
                        Program.Log($"Project {project.Id} $.TranslateConfig.Source.ProjectRef = '{sourceProject.Id}'");
                        if (doWrite)
                        {
                            // Set the ProjectRef
                            var projectDoc = await connection.FetchAsync <SFProject>(project.Id);

                            await projectDoc.SubmitJson0OpAsync(op =>
                            {
                                op.Set(p => p.TranslateConfig.Source.ProjectRef, sourceProject.Id);
                            });
                        }

                        // Record the source mapping
                        sourceMapping.Add(project.Id, sourceProject.Id);
                    }
                    else
                    {
                        Program.Log($"Error Migrating {project.Id} - Source {sourceParatextId} is missing from MongoDB!");
                    }
                }
            }

            // Get the Textdata collection
            string collectionName = this._realtimeService.GetCollectionName <TextData>();
            IMongoCollection <TextData> collection = this._database.GetCollection <TextData>(collectionName);

            // Get the existing textdata object ids from MongoDB
            List <string> textIds = collection.AsQueryable().Select(t => t.Id).ToList();

            if (!doWrite)
            {
                // Add the test text ids, if we are testing
                textIds = textIds.Concat(this._testTextDataIdCollection).Distinct().ToList();
            }

            List <string> sourceTextIds = textIds.Where(t => t.EndsWith(":source", StringComparison.Ordinal)).ToList();

            // Iterate over every text id in database
            foreach (string textId in sourceTextIds)
            {
                // Get the TextData from the database
                TextData?text = await collection
                                .Find(Builders <TextData> .Filter.Eq("_id", textId))
                                .SingleOrDefaultAsync();

                // If we are testing, see if we have an id in the our test collection
                if (!doWrite && text == null && this._testTextDataIdCollection.Contains(textId))
                {
                    // Build a hollow text data object for test purposes
                    text = new TextData()
                    {
                        Id = textId
                    };
                }

                // If we have the specified id in the database
                if (text != null)
                {
                    // Create with new _id for source text, deleting the old one
                    // You cannot rename _id's, and the client will download a new object with the new id anyway
                    string   oldId       = text.Id;
                    string[] textIdParts = oldId.Split(':', StringSplitOptions.RemoveEmptyEntries);
                    if (textIdParts.Length == 4 && textIdParts.Last() == "source")
                    {
                        string targetId = textIdParts.First();
                        if (sourceMapping.ContainsKey(targetId))
                        {
                            text.Id = $"{sourceMapping[targetId]}:{textIdParts[1]}:{textIdParts[2]}:target";
                            if (!textIds.Contains(text.Id))
                            {
                                textIds.Add(text.Id);
                                Program.Log($"Rename TextData {oldId} to {text.Id}");
                                if (doWrite)
                                {
                                    // NOTE: You cannot rename _id in MongoDB!

                                    // Delete from ShareDB
                                    IDocument <TextData> oldTextDoc = connection.Get <TextData>(oldId);
                                    await oldTextDoc.FetchAsync();

                                    if (oldTextDoc.IsLoaded)
                                    {
                                        await oldTextDoc.DeleteAsync();
                                    }

                                    // Remove from MongoDB
                                    await this.DeleteDocsAsync("texts", oldId);

                                    // Add the new text document via the real time service
                                    await connection.CreateAsync(text.Id, text);
                                }
                            }
                            else
                            {
                                // Remove the source, as the it already exists as a target
                                Program.Log($"TextData {text.Id} already exists, deleting {oldId}");
                                if (doWrite)
                                {
                                    // Delete from ShareDB
                                    IDocument <TextData> oldTextDoc = connection.Get <TextData>(oldId);
                                    await oldTextDoc.FetchAsync();

                                    if (oldTextDoc.IsLoaded)
                                    {
                                        await oldTextDoc.DeleteAsync();
                                    }

                                    // Remove from MongoDB
                                    await this.DeleteDocsAsync("texts", oldId);
                                }
                            }
                        }
                        else
                        {
                            Program.Log($"Error Migrating TextData {text.Id} - Missing Source Mapping");
                        }
                    }
                    else
                    {
                        Program.Log($"Error Migrating TextData {text.Id} - Incorrect Identifier Format");
                    }
                }
                else
                {
                    Program.Log($"Error Migrating TextData {textId} - Could Not Load From MongoDB");
                }
            }
        }
Beispiel #7
0
        /// <summary>
        /// Gets the permissions for a project or resource.
        /// </summary>
        /// <param name="userSecret">The user secret.</param>
        /// <param name="project">The project - the UserRoles and ParatextId are used.</param>
        /// <param name="ptUsernameMapping">A mapping of user ID to Paratext username.</param>
        /// <param name="book">The book number. Set to zero to check for all books.</param>
        /// <param name="chapter">The chapter number. Set to zero to check for all books.</param>
        /// <returns>
        /// A dictionary of permissions where the key is the user ID and the value is the permission.
        /// </returns>
        /// <remarks>
        /// See <see cref="TextInfoPermission" /> for permission values.
        /// A dictionary is returned, as permissions can be updated.
        /// </remarks>
        public async Task <Dictionary <string, string> > GetPermissionsAsync(UserSecret userSecret, SFProject project,
                                                                             IReadOnlyDictionary <string, string> ptUsernameMapping, int book = 0, int chapter = 0)
        {
            var permissions = new Dictionary <string, string>();

            // See if the source is a resource
            if (project.ParatextId.Length == SFInstallableDblResource.ResourceIdentifierLength)
            {
                foreach (string uid in project.UserRoles.Keys)
                {
                    permissions.Add(uid, await this.GetResourcePermissionAsync(project.ParatextId, uid));
                }
            }
            else
            {
                // Get the scripture text so we can retrieve the permissions from the XML
                ScrText scrText = ScrTextCollection.FindById(GetParatextUsername(userSecret), project.ParatextId);

                // Calculate the project and resource permissions
                foreach (string uid in project.UserRoles.Keys)
                {
                    // See if the user is in the project members list
                    if (!ptUsernameMapping.TryGetValue(uid, out string userName) || string.IsNullOrWhiteSpace(userName) ||
                        scrText.Permissions.GetRole(userName) == Paratext.Data.Users.UserRoles.None)
                    {
                        permissions.Add(uid, TextInfoPermission.None);
                    }
                    else
                    {
                        string textInfoPermission = TextInfoPermission.Read;
                        if (book == 0)
                        {
                            // Project level
                            if (scrText.Permissions.CanEditAllBooks(userName))
                            {
                                textInfoPermission = TextInfoPermission.Write;
                            }
                        }
                        else if (chapter == 0)
                        {
                            // Book level
                            IEnumerable <int> editable = scrText.Permissions.GetEditableBooks(
                                Paratext.Data.Users.PermissionSet.Merged, userName);
                            if (editable == null || !editable.Any())
                            {
                                // If there are no editable book permissions, check if they can edit all books
                                if (scrText.Permissions.CanEditAllBooks(userName))
                                {
                                    textInfoPermission = TextInfoPermission.Write;
                                }
                            }
                            else if (editable.Contains(book))
                            {
                                textInfoPermission = TextInfoPermission.Write;
                            }
                        }
                        else
                        {
                            // Chapter level
                            IEnumerable <int> editable = scrText.Permissions.GetEditableChapters(book,
                                                                                                 scrText.Settings.Versification, userName, Paratext.Data.Users.PermissionSet.Merged);
                            if (editable?.Contains(chapter) ?? false)
                            {
                                textInfoPermission = TextInfoPermission.Write;
                            }
                        }

                        permissions.Add(uid, textInfoPermission);
                    }
                }
            }

            return(permissions);
        }