mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 16:11:45 +03:00
Allow project manager to read files (#11204)
close #11187 Changelog: - add: `--filesystem-read-path` project manager command to read a file path and return its contents to stdout
This commit is contained in:
parent
7f9cf7a916
commit
0b8a0c493a
@ -32,6 +32,7 @@ transport formats, please look [here](./protocol-architecture.md).
|
||||
- [Create Directory](#create-directory)
|
||||
- [Delete Directory](#delete-directory)
|
||||
- [Move File Or Directory](#move-file-or-directory)
|
||||
- [Read File](#read-file)
|
||||
- [Write to File](#write-to-file)
|
||||
- [Project Management Operations](#project-management-operations)
|
||||
- [`project/open`](#projectopen)
|
||||
@ -337,6 +338,27 @@ null;
|
||||
|
||||
#### Errors
|
||||
|
||||
- [`ProjectDataStoreError`](#projectdatastoreerror) to signal problems with
|
||||
underlying data store.
|
||||
|
||||
### Read File
|
||||
|
||||
Read the provided path and return the contents to stdout.
|
||||
|
||||
#### Parameters
|
||||
|
||||
```typescript
|
||||
project-manager --filesystem-read-path {path}
|
||||
```
|
||||
|
||||
### Result
|
||||
|
||||
```typescript
|
||||
null;
|
||||
```
|
||||
|
||||
#### Errors
|
||||
|
||||
- [`ProjectDataStoreError`](#projectdatastoreerror) to signal problems with
|
||||
underlying data store.
|
||||
|
||||
|
@ -23,6 +23,7 @@ object Cli {
|
||||
val FILESYSTEM_DELETE = "filesystem-delete"
|
||||
val FILESYSTEM_MOVE_FROM = "filesystem-move-from"
|
||||
val FILESYSTEM_MOVE_TO = "filesystem-move-to"
|
||||
val FILESYSTEM_READ_PATH = "filesystem-read-path"
|
||||
val FILESYSTEM_WRITE_PATH = "filesystem-write-path"
|
||||
|
||||
object option {
|
||||
@ -139,6 +140,14 @@ object Cli {
|
||||
.desc("Move directory. Destination.")
|
||||
.build()
|
||||
|
||||
val filesystemReadPath: cli.Option = cli.Option.builder
|
||||
.hasArg(true)
|
||||
.numberOfArgs(1)
|
||||
.argName("path")
|
||||
.longOpt(FILESYSTEM_READ_PATH)
|
||||
.desc("Read the contents of the provided file")
|
||||
.build()
|
||||
|
||||
val filesystemWritePath: cli.Option = cli.Option.builder
|
||||
.hasArg(true)
|
||||
.numberOfArgs(1)
|
||||
@ -165,6 +174,7 @@ object Cli {
|
||||
.addOption(option.filesystemDelete)
|
||||
.addOption(option.filesystemMoveFrom)
|
||||
.addOption(option.filesystemMoveTo)
|
||||
.addOption(option.filesystemReadPath)
|
||||
.addOption(option.filesystemWritePath)
|
||||
|
||||
/** Parse the command line options. */
|
||||
|
@ -15,6 +15,7 @@ import org.enso.projectmanager.boot.command.filesystem.{
|
||||
FileSystemExistsCommand,
|
||||
FileSystemListCommand,
|
||||
FileSystemMoveDirectoryCommand,
|
||||
FileSystemReadPathCommand,
|
||||
FileSystemWritePathCommand
|
||||
}
|
||||
import org.enso.projectmanager.boot.command.{CommandHandler, ProjectListCommand}
|
||||
@ -254,14 +255,22 @@ object ProjectManager extends ZIOAppDefault with LazyLogging {
|
||||
to.toFile
|
||||
)
|
||||
commandHandler.printJson(fileSystemMoveDirectoryCommand.run)
|
||||
} else if (options.hasOption(Cli.FILESYSTEM_READ_PATH)) {
|
||||
val path = Paths.get(options.getOptionValue(Cli.FILESYSTEM_READ_PATH))
|
||||
val fileSystemReadPathCommand =
|
||||
FileSystemReadPathCommand[ZIO[ZAny, +*, +*]](
|
||||
config,
|
||||
path.toFile
|
||||
)
|
||||
commandHandler.printJsonErr(fileSystemReadPathCommand.run)
|
||||
} else if (options.hasOption(Cli.FILESYSTEM_WRITE_PATH)) {
|
||||
val path = Paths.get(options.getOptionValue(Cli.FILESYSTEM_WRITE_PATH))
|
||||
val fileSystemMoveDirectoryCommand =
|
||||
val fileSystemWritePathCommand =
|
||||
FileSystemWritePathCommand[ZIO[ZAny, +*, +*]](
|
||||
config,
|
||||
path.toFile
|
||||
)
|
||||
commandHandler.printJson(fileSystemMoveDirectoryCommand.run)
|
||||
commandHandler.printJson(fileSystemWritePathCommand.run)
|
||||
} else if (options.hasOption(Cli.PROJECT_LIST)) {
|
||||
val projectsPathOpt =
|
||||
Option(options.getOptionValue(Cli.PROJECTS_DIRECTORY))
|
||||
|
@ -8,6 +8,12 @@ import zio.{Console, ExitCode, ZAny, ZIO}
|
||||
|
||||
final class CommandHandler(protocol: Protocol) {
|
||||
|
||||
/** Print the command result to the stdout.
|
||||
*
|
||||
* @param result the command result
|
||||
* @tparam E the error type
|
||||
* @return the program exit code
|
||||
*/
|
||||
def printJson[E: FailureMapper](
|
||||
result: ZIO[ZAny, E, Any]
|
||||
): ZIO[ZAny, Throwable, ExitCode] = {
|
||||
@ -33,6 +39,30 @@ final class CommandHandler(protocol: Protocol) {
|
||||
.map(_ => SuccessExitCode)
|
||||
}
|
||||
|
||||
/** Print only the error command result to the stdout suppressing the successful outcome.
|
||||
*
|
||||
* @param result the command result
|
||||
* @tparam E the error type
|
||||
* @return the program exit code
|
||||
*/
|
||||
def printJsonErr[E: FailureMapper](
|
||||
result: ZIO[ZAny, E, Any]
|
||||
): ZIO[ZAny, Throwable, ExitCode] = {
|
||||
consoleLoggingOff *>
|
||||
result
|
||||
.foldZIO(
|
||||
e => {
|
||||
val error = FailureMapper[E].mapFailure(e)
|
||||
val errorData =
|
||||
JsonProtocol.ErrorData(error.code, error.message, error.payload)
|
||||
val response = JsonProtocol.ResponseError(None, errorData)
|
||||
Console.printLine(JsonProtocol.encode(response))
|
||||
},
|
||||
_ => ZIO.succeed(())
|
||||
)
|
||||
.map(_ => SuccessExitCode)
|
||||
}
|
||||
|
||||
private def consoleLoggingOff: ZIO[ZAny, Throwable, Unit] =
|
||||
ZIO.attempt {
|
||||
val loggerSetup = LoggerSetup.get()
|
||||
|
@ -0,0 +1,55 @@
|
||||
package org.enso.projectmanager.boot.command.filesystem
|
||||
|
||||
import org.enso.projectmanager.boot.configuration.ProjectManagerConfig
|
||||
import org.enso.projectmanager.control.core.syntax._
|
||||
import org.enso.projectmanager.control.core.{Applicative, CovariantFlatMap}
|
||||
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
||||
import org.enso.projectmanager.infrastructure.desktop.DesktopTrash
|
||||
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem
|
||||
import org.enso.projectmanager.infrastructure.random.SystemGenerator
|
||||
import org.enso.projectmanager.infrastructure.repository.ProjectFileRepositoryFactory
|
||||
import org.enso.projectmanager.infrastructure.time.RealClock
|
||||
import org.enso.projectmanager.protocol.FileSystemManagementApi.FileSystemReadPath
|
||||
import org.enso.projectmanager.service.filesystem.{
|
||||
FileSystemService,
|
||||
FileSystemServiceApi,
|
||||
FileSystemServiceFailure
|
||||
}
|
||||
|
||||
import java.io.{File, OutputStream}
|
||||
|
||||
final class FileSystemReadPathCommand[F[+_, +_]: CovariantFlatMap](
|
||||
service: FileSystemServiceApi[F],
|
||||
path: File,
|
||||
output: OutputStream
|
||||
) {
|
||||
|
||||
def run: F[FileSystemServiceFailure, FileSystemReadPath.Result] =
|
||||
service.read(path, output).map { _ => FileSystemReadPath.Result }
|
||||
}
|
||||
|
||||
object FileSystemReadPathCommand {
|
||||
|
||||
def apply[F[+_, +_]: Applicative: CovariantFlatMap: ErrorChannel: Sync](
|
||||
config: ProjectManagerConfig,
|
||||
path: File
|
||||
): FileSystemReadPathCommand[F] = {
|
||||
val clock = new RealClock[F]
|
||||
val fileSystem = new BlockingFileSystem[F](config.timeout.ioTimeout)
|
||||
val gen = new SystemGenerator[F]
|
||||
val trash = DesktopTrash[F]
|
||||
|
||||
val projectRepositoryFactory =
|
||||
new ProjectFileRepositoryFactory[F](
|
||||
config.storage,
|
||||
clock,
|
||||
fileSystem,
|
||||
gen,
|
||||
trash
|
||||
)
|
||||
|
||||
val service = new FileSystemService[F](fileSystem, projectRepositoryFactory)
|
||||
|
||||
new FileSystemReadPathCommand[F](service, path, System.out)
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package org.enso.projectmanager.infrastructure.file
|
||||
import java.io.{File, FileNotFoundException, InputStream}
|
||||
|
||||
import java.io.{File, FileNotFoundException, InputStream, OutputStream}
|
||||
import java.nio.file.{
|
||||
AccessDeniedException,
|
||||
NoSuchFileException,
|
||||
NotDirectoryException
|
||||
}
|
||||
import org.apache.commons.io.{FileExistsException, FileUtils}
|
||||
import org.apache.commons.io.{FileExistsException, FileUtils, IOUtils}
|
||||
import org.enso.projectmanager.control.effect.syntax._
|
||||
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
||||
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem.Encoding
|
||||
@ -22,17 +23,26 @@ class BlockingFileSystem[F[+_, +_]: Sync: ErrorChannel](
|
||||
ioTimeout: FiniteDuration
|
||||
) extends FileSystem[F] {
|
||||
|
||||
/** Reads the contents of a textual file.
|
||||
*
|
||||
* @param file path to the file
|
||||
* @return either [[FileSystemFailure]] or the content of a file as a String
|
||||
*/
|
||||
override def readFile(file: File): F[FileSystemFailure, String] =
|
||||
/** @inheritdoc */
|
||||
override def readTextFile(file: File): F[FileSystemFailure, String] =
|
||||
Sync[F]
|
||||
.blockingOp { FileUtils.readFileToString(file, Encoding) }
|
||||
.mapError(toFsFailure)
|
||||
.timeoutFail(OperationTimeout)(ioTimeout)
|
||||
|
||||
/** @inheritdoc */
|
||||
override def readFile(
|
||||
file: File,
|
||||
output: OutputStream
|
||||
): F[FileSystemFailure, Int] = {
|
||||
Sync[F]
|
||||
.blockingOp {
|
||||
IOUtils.copy(java.nio.file.Files.newInputStream(file.toPath), output)
|
||||
}
|
||||
.mapError(toFsFailure)
|
||||
.timeoutFail(OperationTimeout)(ioTimeout)
|
||||
}
|
||||
|
||||
/** Writes binary content to a file.
|
||||
*
|
||||
* @param file path to the file
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.enso.projectmanager.infrastructure.file
|
||||
|
||||
import java.io.{File, InputStream}
|
||||
import java.io.{File, InputStream, OutputStream}
|
||||
|
||||
/** Represents abstraction for filesystem operations.
|
||||
*
|
||||
@ -8,12 +8,20 @@ import java.io.{File, InputStream}
|
||||
*/
|
||||
trait FileSystem[F[+_, +_]] {
|
||||
|
||||
/** Reads the contents of a textual file.
|
||||
/** Read the contents of a textual file.
|
||||
*
|
||||
* @param file path to the file
|
||||
* @return either [[FileSystemFailure]] or the content of a file as a String
|
||||
*/
|
||||
def readFile(file: File): F[FileSystemFailure, String]
|
||||
def readTextFile(file: File): F[FileSystemFailure, String]
|
||||
|
||||
/** Read the contents of a textual file to the provided output.
|
||||
*
|
||||
* @param file path to the file
|
||||
* @param output the output stream consuming the file contents
|
||||
* @return either [[FileSystemFailure]] or the number of bytes read
|
||||
*/
|
||||
def readFile(file: File, output: OutputStream): F[FileSystemFailure, Int]
|
||||
|
||||
/** Writes binary content to a file.
|
||||
*
|
||||
|
@ -35,7 +35,7 @@ class JsonFileStorage[
|
||||
*/
|
||||
override def load(): F[LoadFailure, A] =
|
||||
fileSystem
|
||||
.readFile(path)
|
||||
.readTextFile(path)
|
||||
.mapError(Coproduct[LoadFailure](_))
|
||||
.flatMap(tryDecodeFileContents)
|
||||
|
||||
|
@ -101,6 +101,25 @@ object FileSystemManagementApi {
|
||||
}
|
||||
}
|
||||
|
||||
case object FileSystemReadPath extends Method("filesystem/readPath") {
|
||||
|
||||
case class Params(path: File)
|
||||
|
||||
type Result = Unused.type
|
||||
val Result = Unused
|
||||
|
||||
implicit val hasParams
|
||||
: HasParams.Aux[this.type, FileSystemReadPath.Params] =
|
||||
new HasParams[this.type] {
|
||||
type Params = FileSystemReadPath.Params
|
||||
}
|
||||
|
||||
implicit val hasResult: HasResult.Aux[this.type, Unused.type] =
|
||||
new HasResult[this.type] {
|
||||
type Result = Unused.type
|
||||
}
|
||||
}
|
||||
|
||||
case object FileSystemWritePath extends Method("filesystem/writePath") {
|
||||
|
||||
case class Params(path: File)
|
||||
|
@ -43,6 +43,7 @@ object JsonRpc {
|
||||
.registerRequest(FileSystemCreateDirectory)
|
||||
.registerRequest(FileSystemDeleteDirectory)
|
||||
.registerRequest(FileSystemMoveDirectory)
|
||||
.registerRequest(FileSystemReadPath)
|
||||
.registerRequest(FileSystemWritePath)
|
||||
.finalized()
|
||||
|
||||
|
@ -13,7 +13,7 @@ import org.enso.projectmanager.infrastructure.repository.ProjectRepositoryFactor
|
||||
import org.enso.projectmanager.service.ProjectService
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.io.{File, InputStream}
|
||||
import java.io.{File, InputStream, OutputStream}
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
|
||||
@ -83,6 +83,18 @@ class FileSystemService[F[+_, +_]: Applicative: CovariantFlatMap: ErrorChannel](
|
||||
FileSystemServiceFailure.FileSystem("Failed to copy path")
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def read(
|
||||
path: File,
|
||||
output: OutputStream
|
||||
): F[FileSystemServiceFailure, Int] =
|
||||
fileSystem
|
||||
.readFile(path, output)
|
||||
.mapError { error =>
|
||||
logger.warn("Failed to read path", error)
|
||||
FileSystemServiceFailure.FileSystem("Failed to read path")
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def write(
|
||||
path: File,
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.enso.projectmanager.service.filesystem
|
||||
|
||||
import java.io.{File, InputStream}
|
||||
import java.io.{File, InputStream, OutputStream}
|
||||
|
||||
trait FileSystemServiceApi[F[+_, +_]] {
|
||||
|
||||
@ -44,10 +44,18 @@ trait FileSystemServiceApi[F[+_, +_]] {
|
||||
*/
|
||||
def copy(from: File, to: File): F[FileSystemServiceFailure, Unit]
|
||||
|
||||
/** Read a file to the provided output.
|
||||
*
|
||||
* @param path the file path to write
|
||||
* @param out the output consuming the file contents
|
||||
* @return the number of bytes read
|
||||
*/
|
||||
def read(path: File, out: OutputStream): F[FileSystemServiceFailure, Int]
|
||||
|
||||
/** Writes a file
|
||||
*
|
||||
* @param path the file path to write
|
||||
* @param bytes the file contents
|
||||
* @param in the file contents
|
||||
*/
|
||||
def write(path: File, in: InputStream): F[FileSystemServiceFailure, Unit]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user