Auto create the root folder if not present. (#10444)

Adds `filesystem-exists` to Project Manager CLI.
If the root folder isn't present will add it at start up.

(cherry picked from commit 891f176e9c)
This commit is contained in:
James Dunkerley 2024-07-04 15:58:47 +01:00
parent e7bc0717ee
commit 468e60e39a
11 changed files with 209 additions and 51 deletions

View File

@ -124,58 +124,67 @@ export default class LocalBackend extends Backend {
): Promise<backend.AnyAsset[]> {
const parentIdRaw = query.parentId == null ? null : extractTypeAndId(query.parentId).id
const parentId = query.parentId ?? newDirectoryId(this.projectManager.rootDirectory)
const entries = await this.projectManager.listDirectory(parentIdRaw)
return entries
.map(entry => {
switch (entry.type) {
case projectManager.FileSystemEntryType.DirectoryEntry: {
return {
type: backend.AssetType.directory,
id: newDirectoryId(entry.path),
modifiedAt: entry.attributes.lastModifiedTime,
parentId,
title: fileInfo.fileName(entry.path),
permissions: [],
projectState: null,
labels: [],
description: null,
} satisfies backend.DirectoryAsset
// Check if Root Directory Exists
if (
parentIdRaw == null &&
!(await this.projectManager.exists(this.projectManager.rootDirectory))
) {
await this.projectManager.createDirectory(this.projectManager.rootDirectory)
return []
} else {
const entries = await this.projectManager.listDirectory(parentIdRaw)
return entries
.map(entry => {
switch (entry.type) {
case projectManager.FileSystemEntryType.DirectoryEntry: {
return {
type: backend.AssetType.directory,
id: newDirectoryId(entry.path),
modifiedAt: entry.attributes.lastModifiedTime,
parentId,
title: fileInfo.fileName(entry.path),
permissions: [],
projectState: null,
labels: [],
description: null,
} satisfies backend.DirectoryAsset
}
case projectManager.FileSystemEntryType.ProjectEntry: {
return {
type: backend.AssetType.project,
id: newProjectId(entry.metadata.id),
title: entry.metadata.name,
modifiedAt: entry.metadata.lastOpened ?? entry.metadata.created,
parentId,
permissions: [],
projectState: {
type:
this.projectManager.projects.get(entry.metadata.id)?.state ??
backend.ProjectState.closed,
volumeId: '',
path: entry.path,
},
labels: [],
description: null,
} satisfies backend.ProjectAsset
}
case projectManager.FileSystemEntryType.FileEntry: {
return {
type: backend.AssetType.file,
id: newFileId(entry.path),
title: fileInfo.fileName(entry.path),
modifiedAt: entry.attributes.lastModifiedTime,
parentId,
permissions: [],
projectState: null,
labels: [],
description: null,
} satisfies backend.FileAsset
}
}
case projectManager.FileSystemEntryType.ProjectEntry: {
return {
type: backend.AssetType.project,
id: newProjectId(entry.metadata.id),
title: entry.metadata.name,
modifiedAt: entry.metadata.lastOpened ?? entry.metadata.created,
parentId,
permissions: [],
projectState: {
type:
this.projectManager.projects.get(entry.metadata.id)?.state ??
backend.ProjectState.closed,
volumeId: '',
path: entry.path,
},
labels: [],
description: null,
} satisfies backend.ProjectAsset
}
case projectManager.FileSystemEntryType.FileEntry: {
return {
type: backend.AssetType.file,
id: newFileId(entry.path),
title: fileInfo.fileName(entry.path),
modifiedAt: entry.attributes.lastModifiedTime,
parentId,
permissions: [],
projectState: null,
labels: [],
description: null,
} satisfies backend.FileAsset
}
}
})
.sort(backend.compareAssets)
})
.sort(backend.compareAssets)
}
}
/** Return a list of projects belonging to the current user.

View File

@ -415,6 +415,20 @@ export default class ProjectManager {
return await this.sendRequest<VersionList>('engine/list-available', {})
}
/** Checks if a file or directory exists. */
async exists(parentId: Path | null) {
/** The type of the response body of this endpoint. */
interface ResponseBody {
readonly exists: boolean
}
const response = await this.runStandaloneCommand<ResponseBody>(
null,
'filesystem-exists',
parentId ?? this.rootDirectory
)
return response.exists
}
/** List directories, projects and files in the given folder. */
async listDirectory(parentId: Path | null) {
/** The type of the response body of this endpoint. */

View File

@ -214,6 +214,17 @@ export default function projectManagerShimMiddleware(
})
try {
switch (cliArguments[0]) {
case '--filesystem-exists': {
const directoryPath = cliArguments[1]
if (directoryPath != null) {
const exists = await fs
.access(directoryPath)
.then(() => true)
.catch(() => false)
result = toJSONRPCResult({ exists })
}
break
}
case '--filesystem-list': {
const directoryPath = cliArguments[1]
if (directoryPath != null) {

View File

@ -17,6 +17,7 @@ object Cli {
val PROJECTS_DIRECTORY = "projects-directory"
val PROJECT_LIST = "project-list"
val FILESYSTEM_EXISTS = "filesystem-exists"
val FILESYSTEM_LIST = "filesystem-list"
val FILESYSTEM_CREATE_DIRECTORY = "filesystem-create-directory"
val FILESYSTEM_DELETE = "filesystem-delete"
@ -90,6 +91,14 @@ object Cli {
.desc("List user projects.")
.build()
val filesystemExists: cli.Option = cli.Option.builder
.hasArg(true)
.numberOfArgs(1)
.argName("path")
.longOpt(FILESYSTEM_EXISTS)
.desc("Check if a file or directory exists.")
.build()
val filesystemList: cli.Option = cli.Option.builder
.hasArg(true)
.numberOfArgs(1)
@ -150,6 +159,7 @@ object Cli {
.addOption(option.profilingTime)
.addOption(option.projectsDirectory)
.addOption(option.projectList)
.addOption(option.filesystemExists)
.addOption(option.filesystemList)
.addOption(option.filesystemCreateDirectory)
.addOption(option.filesystemDelete)

View File

@ -12,6 +12,7 @@ import org.enso.projectmanager.boot.Globals.{
import org.enso.projectmanager.boot.command.filesystem.{
FileSystemCreateDirectoryCommand,
FileSystemDeleteCommand,
FileSystemExistsCommand,
FileSystemListCommand,
FileSystemMoveDirectoryCommand,
FileSystemWritePathCommand
@ -218,6 +219,11 @@ object ProjectManager extends ZIOAppDefault with LazyLogging {
ZIO.succeed(SuccessExitCode)
} else if (options.hasOption(Cli.VERSION_OPTION)) {
displayVersion(options.hasOption(Cli.JSON_OPTION))
} else if (options.hasOption(Cli.FILESYSTEM_EXISTS)) {
val path = Paths.get(options.getOptionValue(Cli.FILESYSTEM_EXISTS))
val fileSystemExistsCommand =
FileSystemExistsCommand[ZIO[ZAny, +*, +*]](config, path.toFile)
commandHandler.printJson(fileSystemExistsCommand.run)
} else if (options.hasOption(Cli.FILESYSTEM_LIST)) {
val directory = Paths.get(options.getOptionValue(Cli.FILESYSTEM_LIST))
val fileSystemListCommand =

View File

@ -0,0 +1,51 @@
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.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.FileSystemExists
import org.enso.projectmanager.service.filesystem.{
FileSystemService,
FileSystemServiceApi,
FileSystemServiceFailure
}
import java.io.File
final class FileSystemExistsCommand[
F[+_, +_]: CovariantFlatMap
](service: FileSystemServiceApi[F], path: File) {
def run: F[FileSystemServiceFailure, FileSystemExists.Result] =
service
.exists(path)
.map(FileSystemExists.Result)
}
object FileSystemExistsCommand {
def apply[F[+_, +_]: Applicative: CovariantFlatMap: ErrorChannel: Sync](
config: ProjectManagerConfig,
path: File
): FileSystemExistsCommand[F] = {
val clock = new RealClock[F]
val fileSystem = new BlockingFileSystem[F](config.timeout.ioTimeout)
val gen = new SystemGenerator[F]
val projectRepositoryFactory = new ProjectFileRepositoryFactory[F](
config.storage,
clock,
fileSystem,
gen
)
val service = new FileSystemService[F](fileSystem, projectRepositoryFactory)
new FileSystemExistsCommand[F](service, path)
}
}

View File

@ -24,6 +24,23 @@ object FileSystemManagementApi {
}
}
case object FileSystemExists extends Method("filesystem/exists") {
case class Params(path: File)
case class Result(exists: Boolean)
implicit val hasParams: HasParams.Aux[this.type, FileSystemExists.Params] =
new HasParams[this.type] {
type Params = FileSystemExists.Params
}
implicit val hasResult: HasResult.Aux[this.type, FileSystemExists.Result] =
new HasResult[this.type] {
type Result = FileSystemExists.Result
}
}
case object FileSystemCreateDirectory
extends Method("filesystem/createDirectory") {

View File

@ -38,6 +38,7 @@ object JsonRpc {
.registerRequest(ConfigDelete)
.registerRequest(LoggingServiceGetEndpoint)
.registerRequest(FileSystemList)
.registerRequest(FileSystemExists)
.registerRequest(FileSystemCreateDirectory)
.registerRequest(FileSystemDeleteDirectory)
.registerRequest(FileSystemMoveDirectory)

View File

@ -21,6 +21,14 @@ class FileSystemService[F[+_, +_]: Applicative: CovariantFlatMap: ErrorChannel](
projectRepositoryFactory: ProjectRepositoryFactory[F]
) extends FileSystemServiceApi[F] {
/** @inheritdoc */
override def exists(path: File): F[FileSystemServiceFailure, Boolean] =
fileSystem
.exists(path)
.mapError(_ =>
FileSystemServiceFailure.FileSystem("Failed to check if path exists")
)
/** @inheritdoc */
override def list(
path: File

View File

@ -4,6 +4,13 @@ import java.io.{File, InputStream}
trait FileSystemServiceApi[F[+_, +_]] {
/** Checks if the file or directory exists.
*
* @param path the file or directory to check
* @return true if the file or directory exists, false otherwise
*/
def exists(path: File): F[FileSystemServiceFailure, Boolean]
/** List file system entries in the provided directory
*
* @param path the directory to list

View File

@ -204,5 +204,29 @@ class FileSystemServiceSpec
FileUtils.deleteQuietly(filePath)
}
"check existence of a path" in {
implicit val client: WsTestClient = new WsTestClient(address)
val testDir = testStorageConfig.userProjectsPath
val projectName = "New_Project_1"
createProject(projectName)
val testFile = new File(testDir, "foo.txt")
val dummyFile = new File(testDir, "foo.exe")
Files.createFile(testFile.toPath)
val result1 = fileSystemService
.exists(testFile)
.unsafeRunSync()
result1.value should be(true)
val result2 = fileSystemService
.exists(dummyFile)
.unsafeRunSync()
result2.value should be(false)
}
}
}