/// <inheritdoc /> public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) { async Task SymlinkBase(bool files) { Task <IReadOnlyList <string> > task; if (files) { task = ioManager.GetFiles(GameStaticFilesSubdirectory, cancellationToken); } else { task = ioManager.GetDirectories(GameStaticFilesSubdirectory, cancellationToken); } var entries = await task.ConfigureAwait(false); await Task.WhenAll(task.Result.Select(async x => { var destPath = ioManager.ConcatPath(destination, ioManager.GetFileName(x)); logger.LogTrace("Symlinking {0} to {1}...", x, destPath); var fileExistsTask = ioManager.FileExists(destPath, cancellationToken); if (await ioManager.DirectoryExists(destPath, cancellationToken).ConfigureAwait(false)) { await ioManager.DeleteDirectory(destPath, cancellationToken).ConfigureAwait(false); } var fileExists = await fileExistsTask.ConfigureAwait(false); if (fileExists) { await ioManager.DeleteFile(destPath, cancellationToken).ConfigureAwait(false); } await symlinkFactory.CreateSymbolicLink(ioManager.ResolvePath(x), ioManager.ResolvePath(destPath), cancellationToken).ConfigureAwait(false); })).ConfigureAwait(false); } using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { await EnsureDirectories(cancellationToken).ConfigureAwait(false); await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false); } }
public Task <IActionResult> ListLogs([FromQuery] int?page, [FromQuery] int?pageSize, CancellationToken cancellationToken) => Paginated( async() => { var path = fileLoggingConfiguration.GetFullLogDirectory(ioManager, assemblyInformationProvider, platformIdentifier); try { var files = await ioManager.GetFiles(path, cancellationToken).ConfigureAwait(false); var tasks = files.Select( async file => new LogFileResponse { Name = ioManager.GetFileName(file), LastModified = await ioManager .GetLastModified( ioManager.ConcatPath(path, file), cancellationToken) .ConfigureAwait(false), }) .ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); return(new PaginatableResult <LogFileResponse>( tasks .AsQueryable() .Select(x => x.Result) .OrderByDescending(x => x.Name))); } catch (IOException ex) { return(new PaginatableResult <LogFileResponse>( Conflict(new ErrorMessageResponse(ErrorCode.IOError) { AdditionalData = ex.ToString(), }))); } }, null, page, pageSize, cancellationToken);
public async Task <IActionResult> ListLogs(CancellationToken cancellationToken) { var path = fileLoggingConfiguration.GetFullLogDirectory(ioManager, assemblyInformationProvider, platformIdentifier); try { var files = await ioManager.GetFiles(path, cancellationToken).ConfigureAwait(false); var tasks = files.Select( async file => new LogFile { Name = ioManager.GetFileName(file), LastModified = await ioManager.GetLastModified( ioManager.ConcatPath(path, file), cancellationToken) .ConfigureAwait(false) }) .ToList(); await Task.WhenAll(tasks).ConfigureAwait(false); var result = tasks .Select(x => x.Result) .OrderByDescending(x => x.Name) .ToList(); return(Ok(result)); } catch (IOException ex) { return(Conflict(new ErrorMessage(ErrorCode.IOError) { AdditionalData = ex.ToString() })); } }
public async Task <IActionResult> Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { if (model == null) { throw new ArgumentNullException(nameof(model)); } if (String.IsNullOrWhiteSpace(model.Name)) { return(BadRequest(new ErrorMessage(ErrorCode.InstanceWhitespaceName))); } var unNormalizedPath = model.Path; var targetInstancePath = NormalizePath(unNormalizedPath); model.Path = targetInstancePath; var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory); bool InstanceIsChildOf(string otherPath) { if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal)) { return(false); } bool sameLength = targetInstancePath.Length == otherPath.Length; char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)]; return(sameLength || dirSeparatorChar == Path.DirectorySeparatorChar || dirSeparatorChar == Path.AltDirectorySeparatorChar); } if (InstanceIsChildOf(installationDirectoryPath)) { return(Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath))); } // Validate it's not a child of any other instance IActionResult earlyOut = null; ulong countOfOtherInstances = 0; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { var newCancellationToken = cts.Token; try { await DatabaseContext .Instances .AsQueryable() .Select(x => new Models.Instance { Path = x.Path }) .ForEachAsync( otherInstance => { if (++countOfOtherInstances >= generalConfiguration.InstanceLimit) { earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached)); } else if (InstanceIsChildOf(otherInstance.Path)) { earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath)); } if (earlyOut != null && !newCancellationToken.IsCancellationRequested) { cts.Cancel(); } }, newCancellationToken) .ConfigureAwait(false); } catch (OperationCanceledException) { cancellationToken.ThrowIfCancellationRequested(); } } if (earlyOut != null) { return(earlyOut); } // Last test, ensure it's in the list of valid paths if (!(generalConfiguration.ValidInstancePaths? .Select(path => NormalizePath(path)) .Any(path => InstanceIsChildOf(path)) ?? true)) { return(BadRequest(new ErrorMessage(ErrorCode.InstanceNotAtWhitelistedPath))); } async Task <bool> DirExistsAndIsNotEmpty() { if (!await ioManager.DirectoryExists(model.Path, cancellationToken).ConfigureAwait(false)) { return(false); } var filesTask = ioManager.GetFiles(model.Path, cancellationToken); var dirsTask = ioManager.GetDirectories(model.Path, cancellationToken); var files = await filesTask.ConfigureAwait(false); var dirs = await dirsTask.ConfigureAwait(false); return(files.Concat(dirs).Any()); } var dirExistsTask = DirExistsAndIsNotEmpty(); bool attached = false; if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) { if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken).ConfigureAwait(false)) { return(Conflict(new ErrorMessage(ErrorCode.InstanceAtExistingPath))); } else { attached = true; } } var newInstance = CreateDefaultInstance(model); DatabaseContext.Instances.Add(newInstance); try { await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); try { // actually reserve it now await ioManager.CreateDirectory(unNormalizedPath, cancellationToken).ConfigureAwait(false); await ioManager.DeleteFile(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false); } catch { // oh shit delete the model DatabaseContext.Instances.Remove(newInstance); // DCT: Operation must always run await DatabaseContext.Save(default).ConfigureAwait(false);
/// <inheritdoc /> public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) { async Task <IReadOnlyList <string> > GetIgnoreFiles() { var ignoreFileBytes = await ioManager.ReadAllBytes(StaticIgnorePath(), cancellationToken).ConfigureAwait(false); var ignoreFileText = Encoding.UTF8.GetString(ignoreFileBytes); var results = new List <string> { StaticIgnoreFile }; //we don't want to lose trailing whitespace on linux using (var reader = new StringReader(ignoreFileText)) { cancellationToken.ThrowIfCancellationRequested(); var line = await reader.ReadLineAsync().ConfigureAwait(false); if (!String.IsNullOrEmpty(line)) { results.Add(line); } } return(results); }; IReadOnlyList <string> ignoreFiles; async Task SymlinkBase(bool files) { Task <IReadOnlyList <string> > task; if (files) { task = ioManager.GetFiles(GameStaticFilesSubdirectory, cancellationToken); } else { task = ioManager.GetDirectories(GameStaticFilesSubdirectory, cancellationToken); } var entries = await task.ConfigureAwait(false); await Task.WhenAll(entries.Select(async x => { var fileName = ioManager.GetFileName(x); bool ignored; if (platformIdentifier.IsWindows) { //need to normalize ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant()); } else { ignored = ignoreFiles.Any(y => fileName == y); } if (ignored) { logger.LogTrace("Ignoring static file {0}...", fileName); return; } var destPath = ioManager.ConcatPath(destination, fileName); logger.LogTrace("Symlinking {0} to {1}...", x, destPath); var fileExistsTask = ioManager.FileExists(destPath, cancellationToken); if (await ioManager.DirectoryExists(destPath, cancellationToken).ConfigureAwait(false)) { await ioManager.DeleteDirectory(destPath, cancellationToken).ConfigureAwait(false); } var fileExists = await fileExistsTask.ConfigureAwait(false); if (fileExists) { await ioManager.DeleteFile(destPath, cancellationToken).ConfigureAwait(false); } await symlinkFactory.CreateSymbolicLink(ioManager.ResolvePath(x), ioManager.ResolvePath(destPath), cancellationToken).ConfigureAwait(false); })).ConfigureAwait(false); } using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { await EnsureDirectories(cancellationToken).ConfigureAwait(false); ignoreFiles = await GetIgnoreFiles().ConfigureAwait(false); await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false); } }
public async Task <IActionResult> Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken) { if (model == null) { throw new ArgumentNullException(nameof(model)); } if (String.IsNullOrWhiteSpace(model.Name)) { return(BadRequest(new ErrorMessage(ErrorCode.InstanceWhitespaceName))); } var targetInstancePath = NormalizePath(model.Path); model.Path = targetInstancePath; var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory); bool InstanceIsChildOf(string otherPath) { if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal)) { return(false); } bool sameLength = targetInstancePath.Length == otherPath.Length; char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)]; return(sameLength || dirSeparatorChar == Path.DirectorySeparatorChar || dirSeparatorChar == Path.AltDirectorySeparatorChar); } if (InstanceIsChildOf(installationDirectoryPath)) { return(Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath))); } // Validate it's not a child of any other instance IActionResult earlyOut = null; ulong countOfOtherInstances = 0; using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { var newCancellationToken = cts.Token; try { await DatabaseContext .Instances .AsQueryable() .Select(x => new Models.Instance { Path = x.Path }) .ForEachAsync( otherInstance => { if (++countOfOtherInstances >= generalConfiguration.InstanceLimit) { earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached)); } else if (InstanceIsChildOf(otherInstance.Path)) { earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath)); } if (earlyOut != null && !newCancellationToken.IsCancellationRequested) { cts.Cancel(); } }, newCancellationToken) .ConfigureAwait(false); } catch (OperationCanceledException) { cancellationToken.ThrowIfCancellationRequested(); } } if (earlyOut != null) { return(earlyOut); } // Last test, ensure it's in the list of valid paths if (!(generalConfiguration.ValidInstancePaths? .Select(path => NormalizePath(path)) .Any(path => InstanceIsChildOf(path)) ?? true)) { return(BadRequest(new ErrorMessage(ErrorCode.InstanceNotAtWhitelistedPath))); } async Task <bool> DirExistsAndIsNotEmpty() { if (!await ioManager.DirectoryExists(model.Path, cancellationToken).ConfigureAwait(false)) { return(false); } var filesTask = ioManager.GetFiles(model.Path, cancellationToken); var dirsTask = ioManager.GetDirectories(model.Path, cancellationToken); var files = await filesTask.ConfigureAwait(false); var dirs = await dirsTask.ConfigureAwait(false); return(files.Concat(dirs).Any()); } var dirExistsTask = DirExistsAndIsNotEmpty(); bool attached = false; if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false)) { if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken).ConfigureAwait(false)) { return(Conflict(new ErrorMessage(ErrorCode.InstanceAtExistingPath))); } else { attached = true; } } var newInstance = new Models.Instance { ConfigurationType = model.ConfigurationType ?? ConfigurationType.Disallowed, DreamDaemonSettings = new DreamDaemonSettings { AllowWebClient = false, AutoStart = false, PrimaryPort = 1337, SecondaryPort = 1338, SecurityLevel = DreamDaemonSecurity.Safe, StartupTimeout = 60, HeartbeatSeconds = 60 }, DreamMakerSettings = new DreamMakerSettings { ApiValidationPort = 1339, ApiValidationSecurityLevel = DreamDaemonSecurity.Safe }, Name = model.Name, Online = false, Path = model.Path, AutoUpdateInterval = model.AutoUpdateInterval ?? 0, ChatBotLimit = model.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit, RepositorySettings = new RepositorySettings { CommitterEmail = "*****@*****.**", CommitterName = assemblyInformationProvider.VersionPrefix, PushTestMergeCommits = false, ShowTestMergeCommitters = false, AutoUpdatesKeepTestMerges = false, AutoUpdatesSynchronize = false, PostTestMergeComment = false }, InstanceUsers = new List <Models.InstanceUser> // give this user full privileges on the instance { InstanceAdminUser() } }; DatabaseContext.Instances.Add(newInstance); try { await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); try { // actually reserve it now await ioManager.CreateDirectory(targetInstancePath, cancellationToken).ConfigureAwait(false); await ioManager.DeleteFile(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false); } catch { // oh shit delete the model DatabaseContext.Instances.Remove(newInstance); await DatabaseContext.Save(default).ConfigureAwait(false);