@@ -2,22 +2,24 @@ package com.wsl.symlinks.vfs
2
2
3
3
import ai.grazie.utils.WeakHashMap
4
4
import com.intellij.ide.AppLifecycleListener
5
- import com.intellij.idea.IdeaLogger
6
5
import com.intellij.openapi.components.service
7
- import com.intellij.openapi.diagnostic.Attachment
8
- import com.intellij.openapi.diagnostic.DefaultLogger
9
6
import com.intellij.openapi.diagnostic.Logger
7
+ import com.intellij.openapi.extensions.ExtensionPointListener
8
+ import com.intellij.openapi.extensions.PluginDescriptor
9
+ import com.intellij.openapi.extensions.impl.ExtensionPointImpl
10
10
import com.intellij.openapi.util.io.FileAttributes
11
11
import com.intellij.openapi.vfs.VirtualFile
12
12
import com.intellij.openapi.vfs.VirtualFileManager
13
13
import com.intellij.openapi.vfs.VirtualFileSystem
14
- import com.intellij.openapi.vfs.impl.VirtualFileManagerImpl
15
14
import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl
15
+ import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile
16
+ import com.intellij.openapi.vfs.newvfs.impl.StubVirtualFile
16
17
import com.intellij.platform.workspace.storage.url.VirtualFileUrl
18
+ import com.intellij.util.KeyedLazyInstance
19
+ import com.intellij.util.KeyedLazyInstanceEP
17
20
import com.intellij.util.io.URLUtil
18
- import com.jetbrains.rd.util.collections.SynchronizedMap
19
21
import java.io.*
20
- import java.util.concurrent.ConcurrentLinkedQueue
22
+ import java.util.concurrent.ConcurrentHashMap
21
23
import java.util.concurrent.LinkedBlockingQueue
22
24
import java.util.concurrent.TimeUnit
23
25
import java.util.concurrent.locks.ReentrantLock
@@ -27,36 +29,10 @@ import kotlin.concurrent.withLock
27
29
28
30
class StartupListener : AppLifecycleListener {
29
31
override fun appFrameCreated (commandLineArgs : MutableList <String >) {
30
- MyLogger .setup()
31
- }
32
- }
33
-
34
- class MyLogger (category : String ): DefaultLogger(category) {
35
-
36
- override fun error (message : String? ) {
37
- if (message?.contains(" >1 file system registered for protocol" ) == true ) {
38
- return
39
- }
40
- super .error(message)
41
- }
42
-
43
- override fun error (message : String? , t : Throwable ? , vararg details : String? ) {
44
- if (message?.contains(" >1 file system registered for protocol" ) == true ) {
45
- return
46
- }
47
- super .error(message, t, * details)
48
- }
49
32
50
- companion object {
51
- fun setup () {
52
- // IdeaLogger.setFactory { category -> MyLogger(category) }
53
- // Logger.setFactory { category -> MyLogger(category) }
54
- }
55
- // val logger = setup()
56
33
}
57
34
}
58
35
59
- val myResourceLock = ReentrantLock ()
60
36
61
37
class WslSymlinksProvider (distro : String ) {
62
38
val LOGGER = Logger .getInstance(WslSymlinksProvider ::class .java)
@@ -65,19 +41,20 @@ class WslSymlinksProvider(distro: String) {
65
41
private var processReader: BufferedReader ? = null
66
42
private var processWriter: BufferedWriter ? = null
67
43
private val queue: LinkedBlockingQueue <AsyncValue > = LinkedBlockingQueue ()
68
- private val mapped: SynchronizedMap <String , AsyncValue > = SynchronizedMap ()
44
+ private val mapped: ConcurrentHashMap <String , AsyncValue > = ConcurrentHashMap ()
45
+ val myResourceLock = ReentrantLock ()
69
46
70
- class AsyncValue {
47
+ class AsyncValue ( private val lock : ReentrantLock ) {
71
48
public val id = this .hashCode().toString()
72
49
public var request: String? = null
73
50
internal var value: String? = null
74
- internal val condition = myResourceLock .newCondition()
51
+ internal val condition = lock .newCondition()
75
52
fun getValue (): String? {
76
53
var elapsed = 0
77
- while (value == null && elapsed < 5000 ) {
78
- if (myResourceLock .tryLock(10 , TimeUnit .MILLISECONDS )) {
54
+ while (value == null && elapsed < 1000 ) {
55
+ if (lock .tryLock(10 , TimeUnit .MILLISECONDS )) {
79
56
condition.await(10 , TimeUnit .MILLISECONDS )
80
- myResourceLock .unlock()
57
+ lock .unlock()
81
58
}
82
59
elapsed + = 10
83
60
}
@@ -89,24 +66,29 @@ class WslSymlinksProvider(distro: String) {
89
66
}
90
67
91
68
init {
92
- val bash = {}.javaClass.getResource(" /files.sh" )?.readText()!!
93
- val location = " \\\\ wsl.localhost\\ $distro \\ var\\ tmp\\ intellij-idea-wsl-symlinks.sh"
94
- File (location).writeText(bash)
95
- val builder = ProcessBuilder (" wsl.exe" , " -d" , distro, " -e" , " bash" , " //var/tmp/intellij-idea-wsl-symlinks.sh" )
69
+ LOGGER .info(" starting WslSymlinksProvider for distro: $distro " )
96
70
97
71
98
72
fun setupProcess () {
73
+ val bash = {}.javaClass.getResource(" /files.sh" )?.readText()!!
74
+ val location = " \\\\ wsl.localhost\\ $distro \\ var\\ tmp\\ intellij-idea-wsl-symlinks.sh"
75
+ File (location).writeText(bash)
76
+ val builder = ProcessBuilder (" wsl.exe" , " -d" , distro, " -e" , " bash" , " //var/tmp/intellij-idea-wsl-symlinks.sh" )
99
77
val process = builder.start()
100
78
this .process = process
101
79
this .processReader = BufferedReader (InputStreamReader (process.inputStream))
102
80
this .processWriter = BufferedWriter (OutputStreamWriter (process.outputStream));
81
+ LOGGER .info(" starting process" )
103
82
process.onExit().whenComplete { t, u ->
104
83
LOGGER .error(" process did exit ${u?.message ? : " no-reason" } " )
105
84
setupProcess()
106
85
}
107
86
}
108
87
109
- setupProcess()
88
+ thread {
89
+ setupProcess()
90
+ }
91
+
110
92
111
93
thread {
112
94
while (true ) {
@@ -123,7 +105,7 @@ class WslSymlinksProvider(distro: String) {
123
105
}
124
106
mapped.remove(id)
125
107
} catch (e: Exception ) {
126
- LOGGER .error(" failed to write " , e)
108
+ LOGGER .error(" failed to read " , e)
127
109
}
128
110
}
129
111
}
@@ -154,13 +136,18 @@ class WslSymlinksProvider(distro: String) {
154
136
if (file.cachedWSLCanonicalPath == null ) {
155
137
try {
156
138
val wslPath = file.getWSLPath()
157
- val a = AsyncValue ()
139
+ val a = AsyncValue (myResourceLock )
158
140
a.request = " ${a.id} ;read-symlink;${wslPath} \n "
159
141
this .queue.add(a)
160
- while (a.getValue() == null ) {
142
+ var n = 0
143
+ while (a.getValue() == null && n < 3 ) {
161
144
if (! queue.contains(a)) {
162
145
this .queue.add(a)
163
146
}
147
+ n + = 1
148
+ }
149
+ if (a.getValue() == null ) {
150
+ return file.path
164
151
}
165
152
val link = a.getValue()
166
153
file.cachedWSLCanonicalPath = file.path.split(" /" ).subList(0 , 4 ).joinToString(" /" ) + link
@@ -179,13 +166,18 @@ class WslSymlinksProvider(distro: String) {
179
166
if (file.isFromWSL() && file.parent != null ) {
180
167
try {
181
168
val path: String = file.path.replace(" ^//wsl\\ $/[^/]+" .toRegex(), " " ).replace(""" ^//wsl.localhost/[^/]+""" .toRegex(), " " )
182
- val a = AsyncValue ()
169
+ val a = AsyncValue (myResourceLock )
183
170
a.request = " ${a.id} ;is-symlink;${path} \n "
184
171
this .queue.add(a)
185
- while (a.getValue() == null ) {
172
+ var n = 0
173
+ while (a.getValue() == null && n < 3 ) {
186
174
if (! queue.contains(a)) {
187
175
this .queue.add(a)
188
176
}
177
+ n + = 1
178
+ }
179
+ if (a.getValue() == null ) {
180
+ return false
189
181
}
190
182
val isSymLink = a.getValue()
191
183
file.isSymlink = isSymLink.equals(" true" )
@@ -200,9 +192,30 @@ class WslSymlinksProvider(distro: String) {
200
192
}
201
193
202
194
class WslVirtualFileSystem : LocalFileSystemImpl () {
195
+ val LOGGER = Logger .getInstance(WslVirtualFileSystem ::class .java)
203
196
private var wslSymlinksProviders: MutableMap <String , WslSymlinksProvider > = HashMap ()
204
- private var reentry: VirtualFile ? = null
205
- val lock = ReentrantLock ()
197
+
198
+ init {
199
+ val classNameToUnregister = LocalFileSystemImpl ::class .java.canonicalName
200
+ VirtualFileSystem .EP_NAME .point.addExtensionPointListener(object : ExtensionPointListener <KeyedLazyInstance <VirtualFileSystem >> {
201
+ override fun extensionRemoved (
202
+ extension : KeyedLazyInstance <VirtualFileSystem >,
203
+ pluginDescriptor : PluginDescriptor
204
+ ) {
205
+ val ext = (extension as ? KeyedLazyInstanceEP )
206
+ if (ext != null ) {
207
+ pluginDescriptor.isEnabled = false
208
+ ext.implementationClass = null
209
+ }
210
+
211
+ }
212
+ }, false , this )
213
+ val point: ExtensionPointImpl <Any > = VirtualFileSystem .EP_NAME .point as ExtensionPointImpl <Any >
214
+ point.unregisterExtensions({ className, adapter ->
215
+ className != " com.intellij.openapi.vfs.impl.VirtualFileManagerImpl\$ VirtualFileSystemBean"
216
+ || adapter.createInstance<KeyedLazyInstanceEP <VirtualFileSystem >>(point.componentManager)?.implementationClass != " com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl" },
217
+ /* stopAfterFirstMatch = */ true )
218
+ }
206
219
207
220
override fun getProtocol (): String {
208
221
return " file"
@@ -217,18 +230,16 @@ class WslVirtualFileSystem: LocalFileSystemImpl() {
217
230
}
218
231
219
232
fun getRealVirtualFile (file : VirtualFile ): VirtualFile {
220
- lock.withLock {
221
- if (reentry == file) {
222
- throw Error (" error" )
223
- }
224
- reentry = file
225
- val symlkinkWsl = file.parents.find { it.isFromWSL() && this .getWslSymlinksProviders(file).isWslSymlink(it) }
226
- val relative = symlkinkWsl?.path?.let { file.path.replace(it, " " ) }
227
- val resolved = symlkinkWsl?.let { virtualFile -> this .resolveSymLink(virtualFile)?.let { this .findFileByPath(it) } }
228
- val r = relative?.let { resolved?.findFileByRelativePath(it) } ? : file
229
- reentry = null
230
- return r
231
- }
233
+ val symlkinkWsl = file.parents.find { it.isFromWSL() && this .getWslSymlinksProviders(file).isWslSymlink(it) }
234
+ val relative = symlkinkWsl?.path?.let { file.path.replace(it, " " ) }
235
+ val resolved = symlkinkWsl?.let { virtualFile -> this .resolveSymLink(virtualFile)?.let { this .findFileByPath(it) } }
236
+ val r = relative?.let { resolved?.findFileByRelativePath(it) } ? : file
237
+ return r
238
+ }
239
+
240
+ override fun getInputStream (vfile : VirtualFile ): InputStream {
241
+ val file = this .getRealVirtualFile(vfile)
242
+ return super .getInputStream(file)
232
243
}
233
244
234
245
override fun contentsToByteArray (vfile : VirtualFile ): ByteArray {
@@ -244,20 +255,30 @@ class WslVirtualFileSystem: LocalFileSystemImpl() {
244
255
override fun getAttributes (vfile : VirtualFile ): FileAttributes ? {
245
256
val file = getRealVirtualFile(vfile)
246
257
var attributes = super .getAttributes(file)
258
+
247
259
if (attributes != null && attributes.type == null && this .getWslSymlinksProviders(file).isWslSymlink(file)) {
248
- val resolved = this .resolveSymLink(file)?.let { this .findFileByPath(it) }
260
+ val resolved = this .resolveSymLink(file)?.let { resPath ->
261
+ return @let object : StubVirtualFile () {
262
+ override fun getPath (): String {
263
+ return resPath
264
+ }
265
+
266
+ override fun getParent (): VirtualFile {
267
+ return vfile.parent
268
+ }
269
+ }
270
+ }
249
271
if (resolved != null ) {
250
272
val resolvedAttrs = super .getAttributes(resolved)
251
273
attributes = FileAttributes (resolvedAttrs?.isDirectory ? : false , false , true , attributes.isHidden, attributes.length, attributes.lastModified, attributes.isWritable, FileAttributes .CaseSensitivity .SENSITIVE )
252
274
}
253
-
254
275
}
255
276
return attributes
256
277
}
257
278
258
279
override fun resolveSymLink (file : VirtualFile ): String? {
259
280
if (file.isFromWSL()) {
260
- return this .getWslSymlinksProviders(file).getWSLCanonicalPath(file);
281
+ return this .getWslSymlinksProviders(file).getWSLCanonicalPath(file)
261
282
}
262
283
return super .resolveSymLink(file)
263
284
}
0 commit comments