mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 02:21:54 +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)
|
- [Create Directory](#create-directory)
|
||||||
- [Delete Directory](#delete-directory)
|
- [Delete Directory](#delete-directory)
|
||||||
- [Move File Or Directory](#move-file-or-directory)
|
- [Move File Or Directory](#move-file-or-directory)
|
||||||
|
- [Read File](#read-file)
|
||||||
- [Write to File](#write-to-file)
|
- [Write to File](#write-to-file)
|
||||||
- [Project Management Operations](#project-management-operations)
|
- [Project Management Operations](#project-management-operations)
|
||||||
- [`project/open`](#projectopen)
|
- [`project/open`](#projectopen)
|
||||||
@ -337,6 +338,27 @@ null;
|
|||||||
|
|
||||||
#### Errors
|
#### 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
|
- [`ProjectDataStoreError`](#projectdatastoreerror) to signal problems with
|
||||||
underlying data store.
|
underlying data store.
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ object Cli {
|
|||||||
val FILESYSTEM_DELETE = "filesystem-delete"
|
val FILESYSTEM_DELETE = "filesystem-delete"
|
||||||
val FILESYSTEM_MOVE_FROM = "filesystem-move-from"
|
val FILESYSTEM_MOVE_FROM = "filesystem-move-from"
|
||||||
val FILESYSTEM_MOVE_TO = "filesystem-move-to"
|
val FILESYSTEM_MOVE_TO = "filesystem-move-to"
|
||||||
|
val FILESYSTEM_READ_PATH = "filesystem-read-path"
|
||||||
val FILESYSTEM_WRITE_PATH = "filesystem-write-path"
|
val FILESYSTEM_WRITE_PATH = "filesystem-write-path"
|
||||||
|
|
||||||
object option {
|
object option {
|
||||||
@ -139,6 +140,14 @@ object Cli {
|
|||||||
.desc("Move directory. Destination.")
|
.desc("Move directory. Destination.")
|
||||||
.build()
|
.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
|
val filesystemWritePath: cli.Option = cli.Option.builder
|
||||||
.hasArg(true)
|
.hasArg(true)
|
||||||
.numberOfArgs(1)
|
.numberOfArgs(1)
|
||||||
@ -165,6 +174,7 @@ object Cli {
|
|||||||
.addOption(option.filesystemDelete)
|
.addOption(option.filesystemDelete)
|
||||||
.addOption(option.filesystemMoveFrom)
|
.addOption(option.filesystemMoveFrom)
|
||||||
.addOption(option.filesystemMoveTo)
|
.addOption(option.filesystemMoveTo)
|
||||||
|
.addOption(option.filesystemReadPath)
|
||||||
.addOption(option.filesystemWritePath)
|
.addOption(option.filesystemWritePath)
|
||||||
|
|
||||||
/** Parse the command line options. */
|
/** Parse the command line options. */
|
||||||
|
@ -15,6 +15,7 @@ import org.enso.projectmanager.boot.command.filesystem.{
|
|||||||
FileSystemExistsCommand,
|
FileSystemExistsCommand,
|
||||||
FileSystemListCommand,
|
FileSystemListCommand,
|
||||||
FileSystemMoveDirectoryCommand,
|
FileSystemMoveDirectoryCommand,
|
||||||
|
FileSystemReadPathCommand,
|
||||||
FileSystemWritePathCommand
|
FileSystemWritePathCommand
|
||||||
}
|
}
|
||||||
import org.enso.projectmanager.boot.command.{CommandHandler, ProjectListCommand}
|
import org.enso.projectmanager.boot.command.{CommandHandler, ProjectListCommand}
|
||||||
@ -254,14 +255,22 @@ object ProjectManager extends ZIOAppDefault with LazyLogging {
|
|||||||
to.toFile
|
to.toFile
|
||||||
)
|
)
|
||||||
commandHandler.printJson(fileSystemMoveDirectoryCommand.run)
|
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)) {
|
} else if (options.hasOption(Cli.FILESYSTEM_WRITE_PATH)) {
|
||||||
val path = Paths.get(options.getOptionValue(Cli.FILESYSTEM_WRITE_PATH))
|
val path = Paths.get(options.getOptionValue(Cli.FILESYSTEM_WRITE_PATH))
|
||||||
val fileSystemMoveDirectoryCommand =
|
val fileSystemWritePathCommand =
|
||||||
FileSystemWritePathCommand[ZIO[ZAny, +*, +*]](
|
FileSystemWritePathCommand[ZIO[ZAny, +*, +*]](
|
||||||
config,
|
config,
|
||||||
path.toFile
|
path.toFile
|
||||||
)
|
)
|
||||||
commandHandler.printJson(fileSystemMoveDirectoryCommand.run)
|
commandHandler.printJson(fileSystemWritePathCommand.run)
|
||||||
} else if (options.hasOption(Cli.PROJECT_LIST)) {
|
} else if (options.hasOption(Cli.PROJECT_LIST)) {
|
||||||
val projectsPathOpt =
|
val projectsPathOpt =
|
||||||
Option(options.getOptionValue(Cli.PROJECTS_DIRECTORY))
|
Option(options.getOptionValue(Cli.PROJECTS_DIRECTORY))
|
||||||
|
@ -8,6 +8,12 @@ import zio.{Console, ExitCode, ZAny, ZIO}
|
|||||||
|
|
||||||
final class CommandHandler(protocol: Protocol) {
|
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](
|
def printJson[E: FailureMapper](
|
||||||
result: ZIO[ZAny, E, Any]
|
result: ZIO[ZAny, E, Any]
|
||||||
): ZIO[ZAny, Throwable, ExitCode] = {
|
): ZIO[ZAny, Throwable, ExitCode] = {
|
||||||
@ -33,6 +39,30 @@ final class CommandHandler(protocol: Protocol) {
|
|||||||
.map(_ => SuccessExitCode)
|
.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] =
|
private def consoleLoggingOff: ZIO[ZAny, Throwable, Unit] =
|
||||||
ZIO.attempt {
|
ZIO.attempt {
|
||||||
val loggerSetup = LoggerSetup.get()
|
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
|
package org.enso.projectmanager.infrastructure.file
|
||||||
import java.io.{File, FileNotFoundException, InputStream}
|
|
||||||
|
import java.io.{File, FileNotFoundException, InputStream, OutputStream}
|
||||||
import java.nio.file.{
|
import java.nio.file.{
|
||||||
AccessDeniedException,
|
AccessDeniedException,
|
||||||
NoSuchFileException,
|
NoSuchFileException,
|
||||||
NotDirectoryException
|
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.syntax._
|
||||||
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
||||||
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem.Encoding
|
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem.Encoding
|
||||||
@ -22,17 +23,26 @@ class BlockingFileSystem[F[+_, +_]: Sync: ErrorChannel](
|
|||||||
ioTimeout: FiniteDuration
|
ioTimeout: FiniteDuration
|
||||||
) extends FileSystem[F] {
|
) extends FileSystem[F] {
|
||||||
|
|
||||||
/** Reads the contents of a textual file.
|
/** @inheritdoc */
|
||||||
*
|
override def readTextFile(file: File): F[FileSystemFailure, String] =
|
||||||
* @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] =
|
|
||||||
Sync[F]
|
Sync[F]
|
||||||
.blockingOp { FileUtils.readFileToString(file, Encoding) }
|
.blockingOp { FileUtils.readFileToString(file, Encoding) }
|
||||||
.mapError(toFsFailure)
|
.mapError(toFsFailure)
|
||||||
.timeoutFail(OperationTimeout)(ioTimeout)
|
.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.
|
/** Writes binary content to a file.
|
||||||
*
|
*
|
||||||
* @param file path to the file
|
* @param file path to the file
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.enso.projectmanager.infrastructure.file
|
package org.enso.projectmanager.infrastructure.file
|
||||||
|
|
||||||
import java.io.{File, InputStream}
|
import java.io.{File, InputStream, OutputStream}
|
||||||
|
|
||||||
/** Represents abstraction for filesystem operations.
|
/** Represents abstraction for filesystem operations.
|
||||||
*
|
*
|
||||||
@ -8,12 +8,20 @@ import java.io.{File, InputStream}
|
|||||||
*/
|
*/
|
||||||
trait FileSystem[F[+_, +_]] {
|
trait FileSystem[F[+_, +_]] {
|
||||||
|
|
||||||
/** Reads the contents of a textual file.
|
/** Read the contents of a textual file.
|
||||||
*
|
*
|
||||||
* @param file path to the file
|
* @param file path to the file
|
||||||
* @return either [[FileSystemFailure]] or the content of a file as a String
|
* @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.
|
/** Writes binary content to a file.
|
||||||
*
|
*
|
||||||
|
@ -35,7 +35,7 @@ class JsonFileStorage[
|
|||||||
*/
|
*/
|
||||||
override def load(): F[LoadFailure, A] =
|
override def load(): F[LoadFailure, A] =
|
||||||
fileSystem
|
fileSystem
|
||||||
.readFile(path)
|
.readTextFile(path)
|
||||||
.mapError(Coproduct[LoadFailure](_))
|
.mapError(Coproduct[LoadFailure](_))
|
||||||
.flatMap(tryDecodeFileContents)
|
.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 object FileSystemWritePath extends Method("filesystem/writePath") {
|
||||||
|
|
||||||
case class Params(path: File)
|
case class Params(path: File)
|
||||||
|
@ -43,6 +43,7 @@ object JsonRpc {
|
|||||||
.registerRequest(FileSystemCreateDirectory)
|
.registerRequest(FileSystemCreateDirectory)
|
||||||
.registerRequest(FileSystemDeleteDirectory)
|
.registerRequest(FileSystemDeleteDirectory)
|
||||||
.registerRequest(FileSystemMoveDirectory)
|
.registerRequest(FileSystemMoveDirectory)
|
||||||
|
.registerRequest(FileSystemReadPath)
|
||||||
.registerRequest(FileSystemWritePath)
|
.registerRequest(FileSystemWritePath)
|
||||||
.finalized()
|
.finalized()
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import org.enso.projectmanager.infrastructure.repository.ProjectRepositoryFactor
|
|||||||
import org.enso.projectmanager.service.ProjectService
|
import org.enso.projectmanager.service.ProjectService
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
import java.io.{File, InputStream}
|
import java.io.{File, InputStream, OutputStream}
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.attribute.BasicFileAttributes
|
import java.nio.file.attribute.BasicFileAttributes
|
||||||
|
|
||||||
@ -83,6 +83,18 @@ class FileSystemService[F[+_, +_]: Applicative: CovariantFlatMap: ErrorChannel](
|
|||||||
FileSystemServiceFailure.FileSystem("Failed to copy path")
|
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 */
|
/** @inheritdoc */
|
||||||
override def write(
|
override def write(
|
||||||
path: File,
|
path: File,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.enso.projectmanager.service.filesystem
|
package org.enso.projectmanager.service.filesystem
|
||||||
|
|
||||||
import java.io.{File, InputStream}
|
import java.io.{File, InputStream, OutputStream}
|
||||||
|
|
||||||
trait FileSystemServiceApi[F[+_, +_]] {
|
trait FileSystemServiceApi[F[+_, +_]] {
|
||||||
|
|
||||||
@ -44,10 +44,18 @@ trait FileSystemServiceApi[F[+_, +_]] {
|
|||||||
*/
|
*/
|
||||||
def copy(from: File, to: File): F[FileSystemServiceFailure, Unit]
|
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
|
/** Writes a file
|
||||||
*
|
*
|
||||||
* @param path the file path to write
|
* @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]
|
def write(path: File, in: InputStream): F[FileSystemServiceFailure, Unit]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user