using System.Collections.Generic; using System.Linq; using System.Threading; using MfGames.Gallium; using MfGames.Nitride; using MfGames.Nitride.IO.Contents; using MfGames.Nitride.IO.Directories; using MfGames.Nitride.IO.Paths; using MfGames.Nitride.Pipelines; namespace CopyFiles; /// /// The single pipeline used by the CopyFiles project. /// public class CopyFilesPipeline : PipelineBase { private readonly AddPathPrefix addPathPrefix; private readonly ClearDirectory clearDirectory; private readonly ReadFiles readFiles; private readonly RemovePathPrefix removePathPrefix; private readonly WriteFiles writeFiles; public CopyFilesPipeline( ReadFiles readFiles, ClearDirectory clearDirectory, WriteFiles writeFiles, RemovePathPrefix removePathPrefix, AddPathPrefix addPathPrefix) { // While we can configure these during runtime, it seems cleaner to // build them up during the constructor to call out the ones that // require runtime data. this.readFiles = readFiles.WithPattern("/input/**/*.txt"); this.clearDirectory = clearDirectory.WithPath("/output"); this.writeFiles = writeFiles; this.removePathPrefix = removePathPrefix.WithPathPrefix("/input"); this.addPathPrefix = addPathPrefix.WithPathPrefix("/output"); } /// public override IAsyncEnumerable RunAsync( IEnumerable _, CancellationToken cancellationToken = default) { // We don't care about the incoming entities which means we can // ignore them and use the entities from the ReadFiles operation // or we can union the entities pass in with the ReadFiles in case // we want to merge them. // // This will read all the files of the given pattern and return them // as an IEnumerable with the Zio components (UPath and // binary contents) set. As a note, this doesn't actually read the // file, just create a pointer to where the file could be read from. // // The path component will always be the relative path to the root, // so `/input/a.txt` in this case since we only have one file. IEnumerable entities = this.readFiles.Run(); // Change the path. The path (Zio.UPath component) stays with the // entity which is why there is no root directory in the writeFiles // operation. Instead, we need to remove the `/input` and replace it // with the `/output`. // // We can do that with a RemovePathPrefix followed by an // AddPathPrefix, or use the more complex version of ChangePaths. // In this case, we are going for easy to learn, so we'll do the // pair. // // We are going to use the chain extension to make it easier to // read. Coming out of this, we will have one entity that fulfills: // // entity.Get == "/output/a.txt" entities = entities .Run(this.removePathPrefix, cancellationToken) .Run(this.addPathPrefix, cancellationToken); // Then we write out the files to the output. First we make sure we // clear out the output. This operation performs an action when it // it is first entered, but otherwise passes all the inputs through. // // We can call the clear directory in three ways. The first is to call // the operation with: // // this.clearDirectory.Run() // // The other is to pass in the entities and get a new list of // modified ones out. In this case, clear doesn't make any changes, // but Entity objects are immutable, so we always work on the list // returned. // // entities = this.clearDirectory.Run(entity) // // The third way is to use an extension on entities which lets us // chain calls, ala Gulp's pipelines. The below code does this along // with writing the files to the output. entities = entities .Run(this.clearDirectory, cancellationToken) .Run(this.writeFiles, cancellationToken); // If we are chaining this pipeline into another, we return the // entities. Otherwise, we can just return an empty list. The // pipeline is async, so it is wrapped in a task, but most // operations are not (or are both). return entities.ToAsyncEnumerable(); } }