public DeployAction TryParseDeploymentInfo(HttpRequestBase request, JObject payload, string targetBranch, out DeploymentInfo deploymentInfo) { deploymentInfo = null; string url = payload.Value<string>("RepositoryUrl"); if (string.IsNullOrWhiteSpace(url) || !url.ToLowerInvariant().Contains("api.onedrive.com")) { return DeployAction.UnknownPayload; } /* Expecting payload to be: { "RepositoryUrl": "xxx", "AccessToken": "xxx" } */ string accessToken = payload.Value<string>("AccessToken"); // keep email and name, so that can be re-used in later commit OneDriveInfo oneDriveInfo = new OneDriveInfo() { Deployer = "OneDrive", RepositoryUrl = url, AccessToken = accessToken, AuthorName = _settings.GetValue("authorName"), AuthorEmail = _settings.GetValue("authorEmail") }; deploymentInfo = oneDriveInfo; deploymentInfo.TargetChangeset = DeploymentManager.CreateTemporaryChangeSet( authorName: oneDriveInfo.AuthorName, authorEmail: oneDriveInfo.AuthorEmail, message: String.Format(CultureInfo.CurrentUICulture, Resources.OneDrive_Synchronizing) ); return DeployAction.ProcessDeployment; }
public async Task SyncBasicTests() { var mockTracer = new Mock<ITracer>(); mockTracer .Setup(m => m.Trace(It.IsAny<string>(), It.IsAny<IDictionary<string, string>>())); var repository = Mock.Of<IRepository>(); var fileSystem = new Mock<IFileSystem>(); var fileBase = new Mock<FileBase>(); var fileInfoFactory = new Mock<IFileInfoFactory>(); var fileInfo = new Mock<FileInfoBase>(); var dirBase = new Mock<DirectoryBase>(); var dirInfoFactory = new Mock<IDirectoryInfoFactory>(); // mock dirInfo to make FileSystemHelpers.DeleteDirectorySafe not throw exception var dirInfoBase = new Mock<DirectoryInfoBase>(); fileSystem.Setup(f => f.File).Returns(fileBase.Object); fileSystem.Setup(f => f.FileInfo).Returns(fileInfoFactory.Object); fileInfoFactory.Setup(f => f.FromFileName(It.IsAny<string>())) .Returns(() => fileInfo.Object); fileSystem.Setup(f => f.Directory).Returns(dirBase.Object); fileSystem.Setup(f => f.DirectoryInfo).Returns(dirInfoFactory.Object); dirInfoFactory.Setup(d => d.FromDirectoryName(It.IsAny<string>())).Returns(dirInfoBase.Object); fileBase.Setup(fb => fb.Exists(It.IsAny<string>())).Returns((string path) => { return (path != null && (path.EndsWith("f-delete") || path.EndsWith("bar.txt"))); }); fileBase.Setup(fb => fb.SetLastWriteTimeUtc(It.IsAny<string>(), It.IsAny<DateTime>())); dirBase.Setup(d => d.Exists(It.IsAny<string>())).Returns((string path) => { return (path != null && (path.EndsWith("f-delete-dir") || path.EndsWith("f2"))); }); FileSystemHelpers.Instance = fileSystem.Object; // prepare change from OneDrive OneDriveModel.OneDriveChange change = new OneDriveModel.OneDriveChange(); change.IsDeleted = false; // prepare OneDriveInfo var info = new OneDriveInfo(); info.AccessToken = "fake-token"; info.RepositoryUrl = "https://api.onedrive.com/v1.0/drive/special/approot:/fake-folder"; info.TargetChangeset = new ChangeSet("id", "authorName", "authorEmail", "message", DateTime.UtcNow); // prepare http handler var handler = new TestMessageHandler((HttpRequestMessage message) => { StringContent content = null; if (message != null && message.RequestUri != null) { if (message.RequestUri.AbsoluteUri.Equals(info.RepositoryUrl)) { content = new StringContent(@"{ 'id': 'fake-id'}", Encoding.UTF8, "application/json"); return new HttpResponseMessage { Content = content }; } else if (message.RequestUri.AbsoluteUri.Equals("https://api.onedrive.com/v1.0/drive/items/fake-id/view.changes")) { content = new StringContent(ViewChangePayload, Encoding.UTF8, "application/json"); return new HttpResponseMessage { Content = content }; } else if (message.RequestUri.AbsoluteUri.EndsWith("items/A6034FFBC93398FD!331") || message.RequestUri.AbsoluteUri.EndsWith("items/A6034FFBC93398FD!330")) { content = new StringContent(@"{ '@content.downloadUrl': 'http://site-does-not-exist.microsoft.com'}", Encoding.UTF8, "application/json"); return new HttpResponseMessage { Content = content }; } } content = new StringContent("test file content", Encoding.UTF8, "application/json"); return new HttpResponseMessage { Content = content }; }); // perform action OneDriveHelper helper = CreateMockOneDriveHelper(handler: handler, tracer: mockTracer.Object); await helper.Sync(info, repository); // verification /* Sycing f2 to wwwroot: There are 6 changes 2 deletion f2\f-delete (existed as file) f2\f-delete-dir (existed as folder) 2 file changes f2\foo.txt (not existed) f2\f22\bar.txt (existed) 2 folder chagnes f2 (existed) f2\f22 (not existed) */ // deletion mockTracer.Verify(t => t.Trace(@"Deleted file D:\home\site\wwwroot\f-delete", It.Is<IDictionary<string, string>>(d => d.Count == 0))); mockTracer.Verify(t => t.Trace(@"Deleted directory D:\home\site\wwwroot\f-delete-dir", It.Is<IDictionary<string, string>>(d => d.Count == 0))); // file changes mockTracer.Verify(t => t.Trace(@"Creating file D:\home\site\wwwroot\foo.txt ...", It.Is<IDictionary<string, string>>(d => d.Count == 0))); mockTracer.Verify(t => t.Trace(@"Updating file D:\home\site\wwwroot\f22\bar.txt ...", It.Is<IDictionary<string, string>>(d => d.Count == 0))); mockTracer.Verify(t => t.Trace(@"Deleted file D:\home\site\wwwroot\f-delete", It.Is<IDictionary<string, string>>(d => d.Count == 0))); mockTracer.Verify(t => t.Trace(@"Deleted directory D:\home\site\wwwroot\f-delete-dir", It.Is<IDictionary<string, string>>(d => d.Count == 0))); // directory changes mockTracer.Verify(t => t.Trace(@"Ignore folder f2", It.Is<IDictionary<string, string>>(d => d.Count == 0))); mockTracer.Verify(t => t.Trace(@"Creating directory D:\home\site\wwwroot\f22 ...", It.Is<IDictionary<string, string>>(d => d.Count == 0))); }
internal async Task <ChangeSet> Sync(OneDriveInfo info, IRepository repository, ITracer tracer) { ChangeSet changeSet = null; string cursor = _settings.GetValue(CursorKey); ChangesResult changes = null; // use incoming tracer since it is background work _tracer = tracer; // We truncate cursor value in filename but keep it unharmed in the trace content using (_tracer.Step("Getting delta changes with cursor: {0}...", cursor.Truncate(5))) using (_tracer.Step("cursor: {0}", cursor)) { changes = await GetChanges(info.TargetChangeset.Id, info.AccessToken, info.RepositoryUrl, cursor); } if (changes == null || changes.Count == 0) { _tracer.Trace("No changes need to be applied."); LogMessage(Resources.OneDriveNoChangesFound); return(changeSet); } // for simplicity, use file changes as effective total _totals = changes.FileChanges.Count > 0 ? changes.FileChanges.Count : changes.Count; string hoststarthtml = Path.Combine(_environment.WebRootPath, Constants.HostingStartHtml); FileSystemHelpers.DeleteFileSafe(hoststarthtml); using (new Timer(UpdateStatusFile, state: info.TargetChangeset.Id, dueTime: TimeSpan.FromSeconds(5), period: TimeSpan.FromSeconds(5))) using (_tracer.Step("Applying {0} changes ...", _totals)) { LogMessage(Resources.OneDriveApplyingChanges, _totals); // perform action seperately, so that can ensure timestamp on directory // e.g two changes: // (new) file /a/b/c.txt // (new) dir /a/b // if created dir first then create file. file creation will trigger folder timestamp change. // which will result in "/a/b" has timestamp from the monent of file creation instead of the timestamp value from server, where value supposed to be set by code specifically. await ApplyChangesParallel(changes.DeletionChanges, info.AccessToken, maxParallelCount : 1, countSuccess : changes.FileChanges.Count == 0); await ApplyChangesParallel(changes.FileChanges, info.AccessToken, maxParallelCount : MaxConcurrentRequests, countSuccess : true); // apply folder changes at last to maintain same timestamp as in OneDrive await ApplyChangesParallel(changes.DirectoryChanges, info.AccessToken, maxParallelCount : 1, countSuccess : changes.FileChanges.Count == 0); _tracer.Trace("{0} succeeded, {1} failed", _successCount, _failedCount); LogMessage(Resources.OneDriveApplyResult, _successCount, _failedCount); string message = _failedCount > 0 ? string.Format(CultureInfo.CurrentCulture, Resources.OneDrive_SynchronizedWithFailure, _successCount, _totals, _failedCount) : string.Format(CultureInfo.CurrentCulture, Resources.OneDrive_Synchronized, _totals); // Commit anyway even partial change if (repository.Commit(message, info.AuthorName, info.AuthorEmail)) { changeSet = repository.GetChangeSet("HEAD"); } if (_failedCount > 0) { // signal deployment failied throw new Exception(string.Format(CultureInfo.CurrentCulture, Resources.OneDriveApplyResult, _successCount, _failedCount)); } } // finally keep a copy of the new cursor _settings.SetValue(CursorKey, changes.Cursor); return(changeSet); }