mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:10:43 +07:00
IJPL-156626 IJent: remove id from IjentApi
It's now possible to create an Ijent NIO filesystem with any URL. Also, it becomes easier to create wrappers for Ijent*Api. Before the change, a wrapper couldn't have implemented `id` fairly: on the first hand, a wrapper must have its own id and shouldn't return the id of its delegate, on the other hand, a wrapper couldn't create and register its own id. GitOrigin-RevId: b3f48e87c6b1fb9dae8d4487834ac542f3e4874d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
11d706dddf
commit
b461a07bd9
@@ -18,7 +18,7 @@ import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import com.intellij.platform.ijent.IjentExecApi
|
||||
import com.intellij.platform.ijent.IjentMissingBinary
|
||||
import com.intellij.platform.ijent.community.impl.nio.asNioFileSystem
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentNioFileSystemProvider
|
||||
import com.intellij.platform.ijent.deploy
|
||||
import com.intellij.platform.ijent.executeProcess
|
||||
import com.intellij.platform.ijent.spi.IjentDeployingStrategy
|
||||
@@ -26,6 +26,7 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.URI
|
||||
import kotlin.io.path.isDirectory
|
||||
|
||||
/**
|
||||
@@ -86,9 +87,15 @@ abstract class AbstractIjentVerificationAction : DumbAwareAction() {
|
||||
}
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
val nioFs = ijent.fs.asNioFileSystem()
|
||||
val path = "/etc"
|
||||
val isDir = nioFs.getPath(path).isDirectory()
|
||||
val isDir =
|
||||
IjentNioFileSystemProvider.getInstance()
|
||||
.newFileSystem(
|
||||
URI("ijent://some-random-string"),
|
||||
IjentNioFileSystemProvider.newFileSystemMap(ijent.fs),
|
||||
).use { nioFs ->
|
||||
nioFs.getPath(path).isDirectory()
|
||||
}
|
||||
withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {
|
||||
Messages.showInfoMessage("$path is directory: $isDir", title)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ internal class IjentNioFileStore(
|
||||
private val ijentFsApi: IjentFileSystemApi,
|
||||
) : FileStore() {
|
||||
override fun name(): String =
|
||||
ijentFsApi.id.toString()
|
||||
ijentFsApi.toString()
|
||||
|
||||
// TODO: uncomment appropriate part of the test com.intellij.platform.ijent.functional.fs.UnixLikeFileSystemTest.test file store
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.intellij.platform.ijent.community.impl.nio
|
||||
|
||||
import com.intellij.platform.ijent.fs.*
|
||||
import kotlinx.coroutines.isActive
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.net.URI
|
||||
import java.nio.file.FileStore
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.PathMatcher
|
||||
@@ -11,15 +13,20 @@ import java.nio.file.attribute.UserPrincipalLookupService
|
||||
|
||||
class IjentNioFileSystem internal constructor(
|
||||
private val fsProvider: IjentNioFileSystemProvider,
|
||||
internal val ijentFs: IjentFileSystemApi,
|
||||
private val onClose: () -> Unit,
|
||||
internal val uri: URI,
|
||||
) : FileSystem() {
|
||||
override fun close() {
|
||||
onClose()
|
||||
fsProvider.close(uri)
|
||||
}
|
||||
|
||||
override fun provider(): IjentNioFileSystemProvider = fsProvider
|
||||
|
||||
val ijentFs: IjentFileSystemApi
|
||||
@ApiStatus.Internal
|
||||
get() =
|
||||
fsProvider.ijentFsApi(uri)
|
||||
?: throw java.nio.file.FileSystemException("`$uri` was removed from IJent FS providers")
|
||||
|
||||
override fun isOpen(): Boolean =
|
||||
ijentFs.coroutineScope.isActive
|
||||
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
package com.intellij.platform.ijent.community.impl.nio
|
||||
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.platform.ijent.IjentId
|
||||
import com.intellij.platform.ijent.IjentSessionRegistry
|
||||
import com.intellij.platform.ijent.community.impl.IjentFsResultImpl
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentNioFileSystemProvider.Companion.newFileSystemMap
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentNioFileSystemProvider.UnixFilePermissionBranch.*
|
||||
import com.intellij.platform.ijent.fs.*
|
||||
import com.intellij.platform.ijent.fs.IjentFileInfo.Type.*
|
||||
import com.intellij.platform.ijent.fs.IjentFileSystemPosixApi.CreateDirectoryException
|
||||
import com.intellij.platform.ijent.fs.IjentPosixFileInfo.Type.Symlink
|
||||
import com.intellij.util.text.nullize
|
||||
import com.sun.nio.file.ExtendedCopyOption
|
||||
import kotlinx.coroutines.job
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.nio.channels.AsynchronousFileChannel
|
||||
@@ -23,11 +22,19 @@ import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.nio.file.attribute.FileAttribute
|
||||
import java.nio.file.attribute.FileAttributeView
|
||||
import java.nio.file.spi.FileSystemProvider
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* This filesystem connects to particular IJent instances.
|
||||
*
|
||||
* A new filesystem can be created with [newFileSystem] and [newFileSystemMap].
|
||||
* The URL must have the scheme "ijent", and the unique identifier of a filesystem
|
||||
* is represented with an authority and a path.
|
||||
*
|
||||
* For accessing WSL use [com.intellij.execution.wsl.ijent.nio.IjentWslNioFileSystemProvider].
|
||||
*/
|
||||
class IjentNioFileSystemProvider : FileSystemProvider() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@@ -35,56 +42,91 @@ class IjentNioFileSystemProvider : FileSystemProvider() {
|
||||
installedProviders()
|
||||
.filterIsInstance<IjentNioFileSystemProvider>()
|
||||
.single()
|
||||
|
||||
@JvmStatic
|
||||
fun newFileSystemMap(ijentFs: IjentFileSystemApi): MutableMap<String, *> =
|
||||
mutableMapOf(KEY_IJENT_FS to ijentFs)
|
||||
|
||||
private const val SCHEME = "ijent"
|
||||
private const val KEY_IJENT_FS = "ijentFs"
|
||||
}
|
||||
|
||||
private val registeredFileSystems = ConcurrentHashMap<IjentId, IjentNioFileSystem>()
|
||||
@JvmInline
|
||||
internal value class CriticalSection<D : Any>(val hidden: D) {
|
||||
inline operator fun <T> invoke(body: D.() -> T): T =
|
||||
synchronized(hidden) {
|
||||
hidden.body()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getScheme(): String = "ijent"
|
||||
private val criticalSection = CriticalSection(object {
|
||||
val authorityRegistry: MutableMap<URI, IjentFileSystemApi> = hashMapOf()
|
||||
})
|
||||
|
||||
override fun newFileSystem(uri: URI, env: MutableMap<String, *>?): IjentNioFileSystem {
|
||||
override fun getScheme(): String = SCHEME
|
||||
|
||||
override fun newFileSystem(uri: URI, env: MutableMap<String, *>): IjentNioFileSystem {
|
||||
@Suppress("NAME_SHADOWING") val uri = uri.normalize()
|
||||
typicalUriChecks(uri)
|
||||
|
||||
if (!uri.path.isNullOrEmpty()) {
|
||||
TODO("Filesystems with non-empty paths are not supported yet.")
|
||||
val ijentFs =
|
||||
try {
|
||||
env[KEY_IJENT_FS] as IjentFileSystemApi
|
||||
}
|
||||
catch (err: Exception) {
|
||||
throw when (err) {
|
||||
is NullPointerException, is ClassCastException ->
|
||||
IllegalArgumentException("Invalid map. `IjentNioFileSystemProvider.newFileSystemMap` should be used for map creation.")
|
||||
else ->
|
||||
err
|
||||
}
|
||||
}
|
||||
val uriParts = getUriParts(uri)
|
||||
criticalSection {
|
||||
for (uriPart in uriParts) {
|
||||
if (uriPart in authorityRegistry) {
|
||||
throw FileSystemAlreadyExistsException(
|
||||
"`$uri` can't be registered because there's an already registered as IJent FS provider `$uriPart`"
|
||||
)
|
||||
}
|
||||
}
|
||||
authorityRegistry[uri] = ijentFs
|
||||
}
|
||||
|
||||
val ijentId = IjentId(uri.host)
|
||||
|
||||
val ijentApi = IjentSessionRegistry.instance().ijents[ijentId]
|
||||
require(ijentApi != null) {
|
||||
"$ijentApi is not registered in ${IjentSessionRegistry::class.java.simpleName}"
|
||||
}
|
||||
|
||||
val fs = IjentNioFileSystem(this, ijentApi.fs, onClose = { registeredFileSystems.remove(ijentId) })
|
||||
|
||||
if (registeredFileSystems.putIfAbsent(ijentId, fs) != null) {
|
||||
throw FileSystemAlreadyExistsException("A filesystem for $ijentId is already registered")
|
||||
}
|
||||
|
||||
fs.ijentFs.coroutineScope.coroutineContext.job.invokeOnCompletion {
|
||||
registeredFileSystems.remove(ijentId)
|
||||
}
|
||||
|
||||
return fs
|
||||
return IjentNioFileSystem(this, uri)
|
||||
}
|
||||
|
||||
override fun newFileSystem(path: Path, env: MutableMap<String, *>?): IjentNioFileSystem =
|
||||
private fun getUriParts(uri: URI): Collection<URI> = uri.path.asSequence()
|
||||
.mapIndexedNotNull { index, c -> if (c == '/') index else null }
|
||||
.map { URI(uri.scheme, uri.authority, uri.path.substring(0, it), null, null) }
|
||||
.plus(sequenceOf(uri))
|
||||
.toList()
|
||||
.asReversed()
|
||||
|
||||
override fun newFileSystem(path: Path, env: MutableMap<String, *>): IjentNioFileSystem =
|
||||
newFileSystem(path.toUri(), env)
|
||||
|
||||
override fun getFileSystem(uri: URI): IjentNioFileSystem {
|
||||
typicalUriChecks(uri)
|
||||
return registeredFileSystems[IjentId(uri.host)] ?: throw FileSystemNotFoundException()
|
||||
val uriParts = getUriParts(uri.normalize())
|
||||
val matchingUri = criticalSection {
|
||||
uriParts.firstOrNull { it in authorityRegistry }
|
||||
}
|
||||
if (matchingUri != null) {
|
||||
return IjentNioFileSystem(this, matchingUri)
|
||||
}
|
||||
else {
|
||||
throw FileSystemNotFoundException("`$uri` is not registered as IJent FS provider")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPath(uri: URI): IjentNioPath =
|
||||
getFileSystem(uri).run {
|
||||
getPath(
|
||||
when (ijentFs) {
|
||||
is IjentFileSystemPosixApi -> uri.path
|
||||
is IjentFileSystemWindowsApi -> uri.path.trimStart('/')
|
||||
}
|
||||
)
|
||||
}
|
||||
override fun getPath(uri: URI): IjentNioPath {
|
||||
val nioFs = getFileSystem(uri)
|
||||
val relativeUri = nioFs.uri.relativize(uri)
|
||||
return nioFs.getPath(
|
||||
when (nioFs.ijentFs) {
|
||||
is IjentFileSystemPosixApi -> relativeUri.path.nullize() ?: "/"
|
||||
is IjentFileSystemWindowsApi -> relativeUri.path.trimStart('/') // TODO Check that uri.path contains the drive letter.
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun newByteChannel(path: Path, options: Set<OpenOption>, vararg attrs: FileAttribute<*>): SeekableByteChannel =
|
||||
newFileChannel(path, options, *attrs)
|
||||
@@ -93,7 +135,7 @@ class IjentNioFileSystemProvider : FileSystemProvider() {
|
||||
ensureIjentNioPath(path)
|
||||
require(path.ijentPath is IjentPath.Absolute)
|
||||
// TODO Handle options and attrs
|
||||
val fs = registeredFileSystems[path.ijentId] ?: throw FileSystemNotFoundException()
|
||||
val fs = path.nioFs
|
||||
|
||||
require(!(READ in options && APPEND in options)) { "READ + APPEND not allowed" }
|
||||
require(!(APPEND in options && TRUNCATE_EXISTING in options)) { "APPEND + TRUNCATE_EXISTING not allowed" }
|
||||
@@ -340,10 +382,10 @@ class IjentNioFileSystemProvider : FileSystemProvider() {
|
||||
override fun <A : BasicFileAttributes> readAttributes(path: Path, type: Class<A>, vararg options: LinkOption): A {
|
||||
val fs = ensureIjentNioPath(path).nioFs
|
||||
|
||||
val result = when (fs.ijentFs) {
|
||||
val result = when (val ijentFs = fs.ijentFs) {
|
||||
is IjentFileSystemPosixApi ->
|
||||
IjentNioPosixFileAttributes(fsBlocking {
|
||||
statPosix(path.ijentPath, fs.ijentFs, LinkOption.NOFOLLOW_LINKS in options)
|
||||
statPosix(path.ijentPath, ijentFs, LinkOption.NOFOLLOW_LINKS in options)
|
||||
})
|
||||
|
||||
is IjentFileSystemWindowsApi -> TODO()
|
||||
@@ -390,6 +432,17 @@ class IjentNioFileSystemProvider : FileSystemProvider() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
internal fun close(uri: URI) {
|
||||
criticalSection {
|
||||
authorityRegistry.remove(uri)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ijentFsApi(uri: URI): IjentFileSystemApi? =
|
||||
criticalSection {
|
||||
authorityRegistry[uri]
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
private fun ensureIjentNioPath(path: Path): IjentNioPath {
|
||||
contract {
|
||||
@@ -416,11 +469,10 @@ class IjentNioFileSystemProvider : FileSystemProvider() {
|
||||
}
|
||||
|
||||
private fun typicalUriChecks(uri: URI) {
|
||||
require(uri.host.isNotEmpty())
|
||||
require(uri.authority.isNotEmpty())
|
||||
|
||||
require(uri.scheme == scheme)
|
||||
require(uri.userInfo.isNullOrEmpty())
|
||||
require(uri.query.isNullOrEmpty())
|
||||
require(uri.fragment.isNullOrEmpty())
|
||||
require(uri.scheme == scheme) { "${uri.scheme} != $scheme" }
|
||||
require(uri.query.isNullOrEmpty()) { uri.query }
|
||||
require(uri.fragment.isNullOrEmpty()) { uri.fragment }
|
||||
}
|
||||
}
|
||||
@@ -12,28 +12,6 @@ import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.startCoroutine
|
||||
|
||||
/**
|
||||
* Returns an adapter from [IjentFileSystemApi] to [java.nio.file.FileSystem]. The adapter is automatically registered in advance,
|
||||
* also it is automatically closed when it is needed.
|
||||
*
|
||||
* The function is idempotent and thread-safe.
|
||||
*/
|
||||
fun IjentFileSystemApi.asNioFileSystem(): FileSystem {
|
||||
val nioFsProvider = IjentNioFileSystemProvider.getInstance()
|
||||
val uri = id.uri
|
||||
return try {
|
||||
nioFsProvider.getFileSystem(uri)
|
||||
}
|
||||
catch (ignored: FileSystemNotFoundException) {
|
||||
try {
|
||||
nioFsProvider.newFileSystem(uri, mutableMapOf<String, Any>())
|
||||
}
|
||||
catch (ignored: FileSystemAlreadyExistsException) {
|
||||
nioFsProvider.getFileSystem(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(FileSystemException::class)
|
||||
internal fun <T, E : IjentFsError> IjentFsResult<T, E>.getOrThrowFileSystemException(): T =
|
||||
when (this) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ijent.community.impl.nio
|
||||
|
||||
import com.intellij.platform.ijent.IjentId
|
||||
import com.intellij.platform.ijent.fs.*
|
||||
import java.net.URI
|
||||
import java.nio.file.*
|
||||
@@ -12,22 +11,20 @@ import java.nio.file.*
|
||||
* How to get a path:
|
||||
* ```kotlin
|
||||
* val ijent: IjentApi = getIjentFromSomewhere()
|
||||
* val path: Path = ijentFs.asNioFileSystem().getPath("/usr/bin/cowsay")
|
||||
* val path: Path = ijent.fs.asNioFileSystem().getPath("/usr/bin/cowsay")
|
||||
* ```
|
||||
*/
|
||||
class IjentNioPath internal constructor(
|
||||
val ijentPath: IjentPath,
|
||||
internal val nioFs: IjentNioFileSystem,
|
||||
) : Path {
|
||||
val ijentId: IjentId get() = nioFs.ijentFs.id
|
||||
|
||||
private val isWindows
|
||||
get() = when (nioFs.ijentFs) {
|
||||
is IjentFileSystemPosixApi -> false
|
||||
is IjentFileSystemWindowsApi -> true
|
||||
}
|
||||
|
||||
override fun getFileSystem(): FileSystem = nioFs
|
||||
override fun getFileSystem(): IjentNioFileSystem = nioFs
|
||||
|
||||
override fun isAbsolute(): Boolean =
|
||||
when (ijentPath) {
|
||||
@@ -106,12 +103,7 @@ class IjentNioPath internal constructor(
|
||||
override fun toUri(): URI =
|
||||
when (ijentPath) {
|
||||
is IjentPath.Absolute ->
|
||||
URI(
|
||||
"ijent",
|
||||
ijentId.id,
|
||||
ijentPath.toString(),
|
||||
null,
|
||||
)
|
||||
nioFs.uri.resolve(ijentPath.toString())
|
||||
|
||||
is IjentPath.Relative ->
|
||||
throw InvalidPathException(toString(), "Can't create a URL from a relative path") // TODO Really no way?
|
||||
|
||||
@@ -13,8 +13,6 @@ import com.intellij.platform.ijent.fs.IjentFileSystemWindowsApi
|
||||
* Usually, [com.intellij.platform.ijent.deploy] creates instances of [IjentApi].
|
||||
*/
|
||||
sealed interface IjentApi : AutoCloseable {
|
||||
val id: IjentId
|
||||
|
||||
val platform: IjentPlatform
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ijent
|
||||
|
||||
import java.net.URI
|
||||
|
||||
/** [id] must be unique across all other instances registered in [IjentSessionRegistry]. */
|
||||
data class IjentId(val id: String) {
|
||||
val uri = URI("ijent", null, id, -1, null, null, null)
|
||||
|
||||
init {
|
||||
try {
|
||||
check(uri.authority == id)
|
||||
}
|
||||
catch (err: Exception) {
|
||||
throw IllegalArgumentException("IjentId must be URI-serializable, but this value isn't: $id", err)
|
||||
}
|
||||
}
|
||||
|
||||
/** [IjentSessionRegistry] creates instances of this class. */
|
||||
class IjentId internal constructor(val id: String) {
|
||||
override fun toString(): String = "IjentId($id)"
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is IjentId && other.id == id
|
||||
|
||||
override fun hashCode(): Int =
|
||||
id.hashCode()
|
||||
}
|
||||
@@ -15,8 +15,6 @@ import kotlin.contracts.contract
|
||||
/** This service should know about all running IJents. */
|
||||
@Service(Service.Level.APP)
|
||||
class IjentSessionRegistry {
|
||||
val ijents: Map<IjentId, IjentApi> get() = ijentsInternal
|
||||
|
||||
private val counter = AtomicLong()
|
||||
|
||||
private val ijentsInternal = ConcurrentHashMap<IjentId, IjentApi>()
|
||||
|
||||
@@ -9,11 +9,6 @@ import java.nio.ByteBuffer
|
||||
// TODO Integrate case-(in)sensitiveness into the interface.
|
||||
|
||||
sealed interface IjentFileSystemApi {
|
||||
/**
|
||||
* The same [IjentId] as in the corresponding [com.intellij.platform.ijent.IjentApi].
|
||||
*/
|
||||
val id: IjentId
|
||||
|
||||
/**
|
||||
* The same [CoroutineScope] as in the corresponding [com.intellij.platform.ijent.IjentApi].
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,11 @@ class IjentSessionMediator private constructor(val scope: CoroutineScope, val pr
|
||||
var expectedErrorCode = ExpectedErrorCode.NO
|
||||
|
||||
companion object {
|
||||
/** See the docs of [IjentSessionMediator] */
|
||||
/**
|
||||
* See the docs of [IjentSessionMediator].
|
||||
*
|
||||
* [ijentId] is used only for logging.
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun create(process: Process, ijentId: IjentId): IjentSessionMediator {
|
||||
val lastStderrMessages = MutableSharedFlow<String?>(
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
package com.intellij.platform.ijent.spi
|
||||
|
||||
import com.intellij.openapi.components.serviceAsync
|
||||
import com.intellij.platform.ijent.IjentApi
|
||||
import com.intellij.platform.ijent.IjentId
|
||||
import com.intellij.platform.ijent.IjentPlatform
|
||||
import com.intellij.platform.ijent.IjentPosixApi
|
||||
import com.intellij.platform.ijent.IjentSessionRegistry
|
||||
import com.intellij.platform.ijent.IjentWindowsApi
|
||||
import com.intellij.platform.ijent.*
|
||||
|
||||
/**
|
||||
* Given that there is some IJent process launched, this extension gets handles to stdin+stdout of the process and returns
|
||||
@@ -18,7 +13,6 @@ interface IjentSessionProvider {
|
||||
* Supposed to be used inside [IjentSessionRegistry.register].
|
||||
*/
|
||||
suspend fun connect(
|
||||
ijentId: IjentId,
|
||||
platform: IjentPlatform,
|
||||
mediator: IjentSessionMediator
|
||||
): IjentApi
|
||||
@@ -44,7 +38,7 @@ sealed class IjentStartupError : RuntimeException {
|
||||
}
|
||||
|
||||
internal class DefaultIjentSessionProvider : IjentSessionProvider {
|
||||
override suspend fun connect(ijentId: IjentId, platform: IjentPlatform, mediator: IjentSessionMediator): IjentApi {
|
||||
override suspend fun connect(platform: IjentPlatform, mediator: IjentSessionMediator): IjentApi {
|
||||
throw IjentStartupError.MissingImplPlugin()
|
||||
}
|
||||
}
|
||||
@@ -60,7 +54,7 @@ suspend fun connectToRunningIjent(ijentName: String, platform: IjentPlatform, pr
|
||||
IjentSessionRegistry.instanceAsync().register(ijentName) { ijentId ->
|
||||
val mediator = IjentSessionMediator.create(process, ijentId)
|
||||
mediator.expectedErrorCode = IjentSessionMediator.ExpectedErrorCode.ZERO
|
||||
IjentSessionProvider.instanceAsync().connect(ijentId, platform, mediator)
|
||||
IjentSessionProvider.instanceAsync().connect(platform, mediator)
|
||||
}
|
||||
|
||||
/** A specialized overload of [connectToRunningIjent] */
|
||||
|
||||
@@ -12,7 +12,7 @@ internal class IjentWslNioFileSystem(
|
||||
private val ijentFs: FileSystem,
|
||||
private val originalFs: FileSystem,
|
||||
) : FileSystem() {
|
||||
override fun toString(): String = """${javaClass.simpleName}(ijentId=${provider.ijentId}, wslLocalRoot=${provider.wslLocalRoot})"""
|
||||
override fun toString(): String = """${javaClass.simpleName}($provider)"""
|
||||
|
||||
override fun close() {
|
||||
ijentFs.close()
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.execution.wsl.ijent.nio
|
||||
|
||||
import com.intellij.execution.wsl.WSLDistribution
|
||||
import com.intellij.openapi.util.io.CaseSensitivityAttribute
|
||||
import com.intellij.openapi.util.io.FileAttributes
|
||||
import com.intellij.platform.core.nio.fs.RoutingAwareFileSystemProvider
|
||||
import com.intellij.platform.ijent.*
|
||||
import com.intellij.platform.ijent.IjentPosixInfo
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentNioPath
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentPosixGroupPrincipal
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentPosixUserPrincipal
|
||||
@@ -30,23 +31,22 @@ import kotlin.io.path.name
|
||||
*
|
||||
* Also, this wrapper delegates calls to the default file system [originalFsProvider]
|
||||
* for the methods not implemented in [ijentFsProvider] yet.
|
||||
*
|
||||
* Normally, there's no need to create this filesystem manually because [com.intellij.execution.wsl.ijent.nio.toggle.IjentWslNioFsToggler]
|
||||
* does this job automatically.
|
||||
* It should be used even for manual creation of filesystems.
|
||||
* Nevertheless, in case when this filesystem should be accessed directly,
|
||||
* an instance of [IjentWslNioFileSystem] can be obtained with a URL like "ijent://wsl/distribution-name".
|
||||
*/
|
||||
internal class IjentWslNioFileSystemProvider(
|
||||
internal val ijentId: IjentId,
|
||||
internal val wslLocalRoot: Path,
|
||||
wslDistribution: WSLDistribution,
|
||||
private val ijentFsProvider: FileSystemProvider,
|
||||
internal val originalFsProvider: FileSystemProvider,
|
||||
) : FileSystemProvider(), RoutingAwareFileSystemProvider {
|
||||
init {
|
||||
require(wslLocalRoot.isAbsolute)
|
||||
}
|
||||
private val ijentFsUri: URI = URI("ijent", "wsl", "/${wslDistribution.id}", null, null)
|
||||
private val wslLocalRoot: Path = originalFsProvider.getFileSystem(URI("file:/")).getPath(wslDistribution.getWindowsPath("/"))
|
||||
|
||||
private val ijentUserInfo: IjentInfo.User by lazy {
|
||||
val api = IjentSessionRegistry.instance().ijents[ijentId] ?: error("The session $ijentId was unregistered")
|
||||
api.info.user
|
||||
}
|
||||
|
||||
override fun toString(): String = """${javaClass.simpleName}(ijentId=$ijentId, wslLocalRoot=$wslLocalRoot)"""
|
||||
override fun toString(): String = """${javaClass.simpleName}(${wslLocalRoot})"""
|
||||
|
||||
override fun canHandleRouting(): Boolean = true
|
||||
|
||||
@@ -54,32 +54,20 @@ internal class IjentWslNioFileSystemProvider(
|
||||
if (this is IjentNioPath)
|
||||
this
|
||||
else
|
||||
fold(ijentFsProvider.getPath(ijentId.uri.resolve("/")) as IjentNioPath, IjentNioPath::resolve)
|
||||
|
||||
private fun Path.toDefaultPath(): Path =
|
||||
if (this is IjentNioPath)
|
||||
wslLocalRoot.resolve(this)
|
||||
else
|
||||
this
|
||||
fold(ijentFsProvider.getPath(ijentFsUri) as IjentNioPath, IjentNioPath::resolve)
|
||||
|
||||
override fun getScheme(): String =
|
||||
originalFsProvider.scheme
|
||||
|
||||
override fun newFileSystem(path: Path, env: MutableMap<String, *>?): IjentWslNioFileSystem {
|
||||
val ijentNioPath = path.toIjentPath()
|
||||
require(ijentNioPath.toUri() == ijentId.uri) { "${ijentNioPath.toUri()} != ${ijentId.uri}" }
|
||||
return IjentWslNioFileSystem(
|
||||
provider = this,
|
||||
ijentFs = ijentFsProvider.newFileSystem(ijentNioPath, env),
|
||||
originalFs = originalFsProvider.newFileSystem(Path.of("."), env),
|
||||
)
|
||||
}
|
||||
override fun newFileSystem(path: Path, env: MutableMap<String, *>?): IjentWslNioFileSystem =
|
||||
getFileSystem(path.toUri())
|
||||
|
||||
override fun getFileSystem(uri: URI): IjentWslNioFileSystem {
|
||||
require(uri == ijentId.uri) { "$uri != ${ijentId.uri}" }
|
||||
require(uri.scheme == scheme) { "Wrong scheme in `$uri` (expected `$scheme`)" }
|
||||
val wslId = wslIdFromPath(originalFsProvider.getPath(uri))
|
||||
return IjentWslNioFileSystem(
|
||||
provider = this,
|
||||
ijentFs = ijentFsProvider.getFileSystem(uri),
|
||||
ijentFs = ijentFsProvider.getFileSystem(URI("ijent", "wsl", "/$wslId", null, null)),
|
||||
originalFsProvider.getFileSystem(URI("file:/"))
|
||||
)
|
||||
}
|
||||
@@ -87,6 +75,13 @@ internal class IjentWslNioFileSystemProvider(
|
||||
override fun newFileSystem(uri: URI, env: MutableMap<String, *>?): IjentWslNioFileSystem =
|
||||
getFileSystem(uri)
|
||||
|
||||
private fun wslIdFromPath(path: Path): String {
|
||||
val root = path.toAbsolutePath().root.toString()
|
||||
require(root.startsWith("""\\wsl""")) { "`$path` doesn't look like a file on WSL" }
|
||||
val wslId = root.removePrefix("""\\wsl""").substringAfter('\\').trimEnd('\\')
|
||||
return wslId
|
||||
}
|
||||
|
||||
override fun checkAccess(path: Path, vararg modes: AccessMode): Unit =
|
||||
ijentFsProvider.checkAccess(path.toIjentPath(), *modes)
|
||||
|
||||
@@ -187,27 +182,19 @@ internal class IjentWslNioFileSystemProvider(
|
||||
// the function always assumes that the returned object is DosFileAttributes on Windows,
|
||||
// and that's always true with the default WindowsFileSystemProvider.
|
||||
|
||||
val actualType = when (ijentUserInfo) {
|
||||
is IjentPosixInfo.User ->
|
||||
if (DosFileAttributes::class.java.isAssignableFrom(type)) PosixFileAttributes::class.java
|
||||
else type
|
||||
val actualType =
|
||||
if (DosFileAttributes::class.java.isAssignableFrom(type)) PosixFileAttributes::class.java
|
||||
else type
|
||||
|
||||
is IjentWindowsInfo.User -> TODO()
|
||||
}
|
||||
|
||||
val actualAttrs = ijentFsProvider.readAttributes(path.toIjentPath(), actualType, *options)
|
||||
|
||||
val resultAttrs = when (actualAttrs) {
|
||||
is DosFileAttributes -> actualAttrs
|
||||
val ijentNioPath = path.toIjentPath()
|
||||
val resultAttrs = when (val actualAttrs = ijentFsProvider.readAttributes(ijentNioPath, actualType, *options)) {
|
||||
is DosFileAttributes -> actualAttrs // TODO How can it be possible? It's certainly known that the remote OS is GNU/Linux.
|
||||
|
||||
is PosixFileAttributes ->
|
||||
when (val ijentUserInfo = ijentUserInfo) {
|
||||
is IjentPosixInfo.User ->
|
||||
IjentNioPosixFileAttributesWithDosAdapter(ijentUserInfo, actualAttrs, path.name.startsWith("."))
|
||||
|
||||
is IjentWindowsInfo.User ->
|
||||
actualAttrs
|
||||
}
|
||||
IjentNioPosixFileAttributesWithDosAdapter(
|
||||
ijentNioPath.fileSystem.ijentFs.user as IjentPosixInfo.User,
|
||||
actualAttrs, path.name.startsWith("."),
|
||||
)
|
||||
|
||||
else -> actualAttrs
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.intellij.execution.wsl.WslIjentManager
|
||||
import com.intellij.execution.wsl.ijent.nio.IjentWslNioFileSystem
|
||||
import com.intellij.execution.wsl.ijent.nio.IjentWslNioFileSystemProvider
|
||||
import com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider
|
||||
import com.intellij.platform.ijent.IjentId
|
||||
import com.intellij.platform.ijent.community.impl.nio.IjentNioFileSystemProvider
|
||||
import com.intellij.platform.ijent.community.impl.nio.telemetry.TracingFileSystemProvider
|
||||
import com.intellij.util.containers.forEachGuaranteed
|
||||
@@ -67,8 +66,7 @@ class IjentWslNioFsToggleStrategy(
|
||||
}
|
||||
|
||||
private suspend fun handleWslDistributionAddition(distro: WSLDistribution) {
|
||||
val ijentApi = WslIjentManager.instanceAsync().getIjentApi(distro, null, false)
|
||||
switchToIjentFs(distro, ijentApi.id)
|
||||
switchToIjentFs(distro)
|
||||
}
|
||||
|
||||
private fun handleWslDistributionDeletion(distro: WSLDistribution) {
|
||||
@@ -78,10 +76,14 @@ class IjentWslNioFsToggleStrategy(
|
||||
}
|
||||
}
|
||||
|
||||
fun switchToIjentFs(distro: WSLDistribution, ijentId: IjentId) {
|
||||
suspend fun switchToIjentFs(distro: WSLDistribution) {
|
||||
val ijentFsProvider = TracingFileSystemProvider(IjentNioFileSystemProvider.getInstance())
|
||||
try {
|
||||
ijentFsProvider.newFileSystem(ijentId.uri, null)
|
||||
val ijentFs = WslIjentManager.instanceAsync().getIjentApi(distro, null, false).fs
|
||||
ijentFsProvider.newFileSystem(
|
||||
URI("ijent", "wsl", "/${distro.id}", null, null),
|
||||
IjentNioFileSystemProvider.newFileSystemMap(ijentFs),
|
||||
)
|
||||
}
|
||||
catch (_: FileSystemAlreadyExistsException) {
|
||||
// Nothing.
|
||||
@@ -93,11 +95,10 @@ class IjentWslNioFsToggleStrategy(
|
||||
}
|
||||
else {
|
||||
IjentWslNioFileSystemProvider(
|
||||
ijentId = ijentId,
|
||||
wslLocalRoot = underlyingProvider.getLocalFileSystem().getPath(distro.getWindowsPath("/")),
|
||||
wslDistribution = distro,
|
||||
ijentFsProvider = ijentFsProvider,
|
||||
originalFsProvider = TracingFileSystemProvider(underlyingProvider),
|
||||
).getFileSystem(ijentId.uri)
|
||||
).getFileSystem(distro.getUNCRootPath().toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider
|
||||
import com.intellij.platform.ijent.IjentId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
@@ -43,9 +42,9 @@ class IjentWslNioFsToggler(private val coroutineScope: CoroutineScope) {
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun switchToIjentFs(distro: WSLDistribution, ijentId: IjentId) {
|
||||
suspend fun switchToIjentFs(distro: WSLDistribution) {
|
||||
strategy ?: error("Not available")
|
||||
strategy.switchToIjentFs(distro, ijentId)
|
||||
strategy.switchToIjentFs(distro)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
|
||||
@@ -504,8 +504,6 @@ class WSLDistributionTest {
|
||||
enum class WslTestStrategy { Legacy, Ijent }
|
||||
|
||||
private class MockIjentApi(private val adapter: GeneralCommandLine, val rootUser: Boolean) : IjentPosixApi {
|
||||
override val id: IjentId get() = throw UnsupportedOperationException()
|
||||
|
||||
override val platform: IjentPlatform get() = throw UnsupportedOperationException()
|
||||
|
||||
override val isRunning: Boolean get() = true
|
||||
|
||||
Reference in New Issue
Block a user