From b461a07bd9a89ce531bbff252948609e1c9ebf1f Mon Sep 17 00:00:00 2001 From: Vladimir Lagunov Date: Fri, 2 Aug 2024 11:11:45 +0200 Subject: [PATCH] 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 --- .../ijent/AbstractIjentVerificationAction.kt | 13 +- .../community/impl/nio/IjentNioFileStore.kt | 2 +- .../community/impl/nio/IjentNioFileSystem.kt | 13 +- .../impl/nio/IjentNioFileSystemProvider.kt | 150 ++++++++++++------ .../impl/nio/IjentNioFileSystemUtil.kt | 22 --- .../ijent/community/impl/nio/IjentNioPath.kt | 14 +- .../com/intellij/platform/ijent/IjentApi.kt | 2 - .../com/intellij/platform/ijent/IjentId.kt | 23 +-- .../platform/ijent/IjentSessionRegistry.kt | 2 - .../platform/ijent/fs/IjentFileSystemApi.kt | 5 - .../ijent/spi/IjentSessionMediator.kt | 6 +- .../ijent/spi/IjentSessionProvider.kt | 12 +- .../wsl/ijent/nio/IjentWslNioFileSystem.kt | 2 +- .../nio/IjentWslNioFileSystemProvider.kt | 83 ++++------ .../ijent/nio/toggle/IjentNioFsStrategy.kt | 17 +- .../ijent/nio/toggle/IjentWslNioFsToggler.kt | 5 +- .../execution/wsl/WSLDistributionTest.kt | 2 - 17 files changed, 188 insertions(+), 185 deletions(-) diff --git a/platform/execution-impl/src/com/intellij/execution/wsl/ijent/AbstractIjentVerificationAction.kt b/platform/execution-impl/src/com/intellij/execution/wsl/ijent/AbstractIjentVerificationAction.kt index d02fef1aae90..4d16e2891441 100644 --- a/platform/execution-impl/src/com/intellij/execution/wsl/ijent/AbstractIjentVerificationAction.kt +++ b/platform/execution-impl/src/com/intellij/execution/wsl/ijent/AbstractIjentVerificationAction.kt @@ -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) } diff --git a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileStore.kt b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileStore.kt index c3cc6c05df17..f62552295b2d 100644 --- a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileStore.kt +++ b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileStore.kt @@ -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 diff --git a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystem.kt b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystem.kt index 0ccf9e0a9bd1..94600ba704d8 100644 --- a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystem.kt +++ b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystem.kt @@ -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 diff --git a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemProvider.kt b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemProvider.kt index cbccde85f60a..fd2de64588c0 100644 --- a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemProvider.kt +++ b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemProvider.kt @@ -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() .single() + + @JvmStatic + fun newFileSystemMap(ijentFs: IjentFileSystemApi): MutableMap = + mutableMapOf(KEY_IJENT_FS to ijentFs) + + private const val SCHEME = "ijent" + private const val KEY_IJENT_FS = "ijentFs" } - private val registeredFileSystems = ConcurrentHashMap() + @JvmInline + internal value class CriticalSection(val hidden: D) { + inline operator fun invoke(body: D.() -> T): T = + synchronized(hidden) { + hidden.body() + } + } - override fun getScheme(): String = "ijent" + private val criticalSection = CriticalSection(object { + val authorityRegistry: MutableMap = hashMapOf() + }) - override fun newFileSystem(uri: URI, env: MutableMap?): IjentNioFileSystem { + override fun getScheme(): String = SCHEME + + override fun newFileSystem(uri: URI, env: MutableMap): 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?): IjentNioFileSystem = + private fun getUriParts(uri: URI): Collection = 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): 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, 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 readAttributes(path: Path, type: Class, 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 } } } \ No newline at end of file diff --git a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemUtil.kt b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemUtil.kt index a1e333281009..a1b818063078 100644 --- a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemUtil.kt +++ b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioFileSystemUtil.kt @@ -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()) - } - catch (ignored: FileSystemAlreadyExistsException) { - nioFsProvider.getFileSystem(uri) - } - } -} - @Throws(FileSystemException::class) internal fun IjentFsResult.getOrThrowFileSystemException(): T = when (this) { diff --git a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioPath.kt b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioPath.kt index c2512f82d46b..c0ba4a044ce4 100644 --- a/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioPath.kt +++ b/platform/ijent/impl/src/com/intellij/platform/ijent/community/impl/nio/IjentNioPath.kt @@ -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? diff --git a/platform/ijent/src/com/intellij/platform/ijent/IjentApi.kt b/platform/ijent/src/com/intellij/platform/ijent/IjentApi.kt index 1398811e4e44..bc4723f0854c 100644 --- a/platform/ijent/src/com/intellij/platform/ijent/IjentApi.kt +++ b/platform/ijent/src/com/intellij/platform/ijent/IjentApi.kt @@ -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 /** diff --git a/platform/ijent/src/com/intellij/platform/ijent/IjentId.kt b/platform/ijent/src/com/intellij/platform/ijent/IjentId.kt index d6ee3c537a00..fa0cdc173f38 100644 --- a/platform/ijent/src/com/intellij/platform/ijent/IjentId.kt +++ b/platform/ijent/src/com/intellij/platform/ijent/IjentId.kt @@ -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() } \ No newline at end of file diff --git a/platform/ijent/src/com/intellij/platform/ijent/IjentSessionRegistry.kt b/platform/ijent/src/com/intellij/platform/ijent/IjentSessionRegistry.kt index b8013546cd8a..5c7eba73cee1 100644 --- a/platform/ijent/src/com/intellij/platform/ijent/IjentSessionRegistry.kt +++ b/platform/ijent/src/com/intellij/platform/ijent/IjentSessionRegistry.kt @@ -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 get() = ijentsInternal - private val counter = AtomicLong() private val ijentsInternal = ConcurrentHashMap() diff --git a/platform/ijent/src/com/intellij/platform/ijent/fs/IjentFileSystemApi.kt b/platform/ijent/src/com/intellij/platform/ijent/fs/IjentFileSystemApi.kt index 4fee4aa674bf..886ba8e8a1dc 100644 --- a/platform/ijent/src/com/intellij/platform/ijent/fs/IjentFileSystemApi.kt +++ b/platform/ijent/src/com/intellij/platform/ijent/fs/IjentFileSystemApi.kt @@ -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]. */ diff --git a/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionMediator.kt b/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionMediator.kt index dd6f63642089..1895b1848e7b 100644 --- a/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionMediator.kt +++ b/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionMediator.kt @@ -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( diff --git a/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionProvider.kt b/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionProvider.kt index 920d60012e5c..deb554e0c997 100644 --- a/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionProvider.kt +++ b/platform/ijent/src/com/intellij/platform/ijent/spi/IjentSessionProvider.kt @@ -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] */ diff --git a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystem.kt b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystem.kt index 6d855e74da72..49c599851620 100644 --- a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystem.kt +++ b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystem.kt @@ -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() diff --git a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystemProvider.kt b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystemProvider.kt index a30234cb7f2b..88a775618a52 100644 --- a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystemProvider.kt +++ b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/IjentWslNioFileSystemProvider.kt @@ -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?): 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?): 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?): 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 } diff --git a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentNioFsStrategy.kt b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentNioFsStrategy.kt index 74284a43a799..7275f00a09fd 100644 --- a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentNioFsStrategy.kt +++ b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentNioFsStrategy.kt @@ -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()) } } } diff --git a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentWslNioFsToggler.kt b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentWslNioFsToggler.kt index b42cc9d4b5a1..1792646ccd06 100644 --- a/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentWslNioFsToggler.kt +++ b/platform/platform-impl/src/com/intellij/execution/wsl/ijent/nio/toggle/IjentWslNioFsToggler.kt @@ -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 diff --git a/platform/platform-tests/testSrc/com/intellij/execution/wsl/WSLDistributionTest.kt b/platform/platform-tests/testSrc/com/intellij/execution/wsl/WSLDistributionTest.kt index d2c5bd99f409..3a14d5b5eab6 100644 --- a/platform/platform-tests/testSrc/com/intellij/execution/wsl/WSLDistributionTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/execution/wsl/WSLDistributionTest.kt @@ -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