|
1 | 1 | import os from 'os';
|
2 | 2 | import path from 'path';
|
| 3 | +import { env } from 'process'; |
3 | 4 | import { Uri, workspace } from 'vscode';
|
| 5 | +import { xdgData } from 'xdg-basedir'; |
4 | 6 | import { Logger } from '../../../system/logger';
|
5 | 7 | import { wait } from '../../../system/promise';
|
6 | 8 | import { getPlatform } from '../platform';
|
7 | 9 |
|
8 |
| -export const sharedGKDataFolder = '.gk'; |
| 10 | +/** @deprecated prefer using XDG paths */ |
| 11 | +const legacySharedGKDataFolder = path.join(os.homedir(), '.gk'); |
9 | 12 |
|
10 |
| -export async function acquireSharedFolderWriteLock(): Promise<boolean> { |
11 |
| - const lockFileUri = getSharedLockFileUri(); |
| 13 | +class SharedGKDataFolderMapper { |
| 14 | + private _initPromise: Promise<void> | undefined; |
| 15 | + constructor( |
| 16 | + // do soft migration, use new folders only for new users (without existing folders) |
| 17 | + // eslint-disable-next-line @typescript-eslint/no-deprecated |
| 18 | + private sharedGKDataFolder = legacySharedGKDataFolder, |
| 19 | + private _isInitialized: boolean = false, |
| 20 | + ) {} |
12 | 21 |
|
13 |
| - let stat; |
14 |
| - while (true) { |
| 22 | + private async _initialize() { |
| 23 | + if (this._initPromise) { |
| 24 | + throw new Error('cannot be initialized multiple times'); |
| 25 | + } |
15 | 26 | try {
|
16 |
| - stat = await workspace.fs.stat(lockFileUri); |
| 27 | + await workspace.fs.stat(Uri.file(this.sharedGKDataFolder)); |
17 | 28 | } catch {
|
18 |
| - // File does not exist, so we can safely create it |
19 |
| - break; |
| 29 | + // Path does not exist, so we can safely use xdg paths |
| 30 | + const platform = getPlatform(); |
| 31 | + const folderName = 'gk'; |
| 32 | + switch (platform) { |
| 33 | + case 'windows': |
| 34 | + if (env.LOCALAPPDATA) { |
| 35 | + this.sharedGKDataFolder = path.join(env.LOCALAPPDATA, folderName, 'Data'); |
| 36 | + } else { |
| 37 | + this.sharedGKDataFolder = path.join(os.homedir(), 'AppData', 'Local', folderName, 'Data'); |
| 38 | + } |
| 39 | + break; |
| 40 | + case 'macOS': |
| 41 | + this.sharedGKDataFolder = path.join(os.homedir(), 'Library', 'Application Support', folderName); |
| 42 | + break; |
| 43 | + default: |
| 44 | + if (xdgData) { |
| 45 | + this.sharedGKDataFolder = path.join(xdgData, folderName); |
| 46 | + } else { |
| 47 | + this.sharedGKDataFolder = path.join(os.homedir(), '.local', 'share', folderName); |
| 48 | + } |
| 49 | + } |
| 50 | + } finally { |
| 51 | + this._isInitialized = true; |
20 | 52 | }
|
| 53 | + } |
21 | 54 |
|
22 |
| - const currentTime = new Date().getTime(); |
23 |
| - if (currentTime - stat.ctime > 30000) { |
24 |
| - // File exists, but the timestamp is older than 30 seconds, so we can safely remove it |
25 |
| - break; |
| 55 | + private async waitForInitialized() { |
| 56 | + if (this._isInitialized) { |
| 57 | + return; |
26 | 58 | }
|
27 |
| - |
28 |
| - // File exists, and the timestamp is less than 30 seconds old, so we need to wait for it to be removed |
29 |
| - await wait(100); |
| 59 | + if (!this._initPromise) { |
| 60 | + this._initPromise = this._initialize(); |
| 61 | + } |
| 62 | + await this._initPromise; |
30 | 63 | }
|
31 | 64 |
|
32 |
| - try { |
33 |
| - // write the lockfile to the shared data folder |
34 |
| - await workspace.fs.writeFile(lockFileUri, new Uint8Array(0)); |
35 |
| - } catch (error) { |
36 |
| - Logger.error(error, 'acquireSharedFolderWriteLock'); |
37 |
| - return false; |
| 65 | + private async getUri(relativeFilePath: string) { |
| 66 | + await this.waitForInitialized(); |
| 67 | + return Uri.file(path.join(this.sharedGKDataFolder, relativeFilePath)); |
38 | 68 | }
|
39 | 69 |
|
40 |
| - return true; |
41 |
| -} |
| 70 | + async acquireSharedFolderWriteLock(): Promise<boolean> { |
| 71 | + const lockFileUri = await this.getUri('lockfile'); |
| 72 | + |
| 73 | + let stat; |
| 74 | + while (true) { |
| 75 | + try { |
| 76 | + stat = await workspace.fs.stat(lockFileUri); |
| 77 | + } catch { |
| 78 | + // File does not exist, so we can safely create it |
| 79 | + break; |
| 80 | + } |
| 81 | + |
| 82 | + const currentTime = new Date().getTime(); |
| 83 | + if (currentTime - stat.ctime > 30000) { |
| 84 | + // File exists, but the timestamp is older than 30 seconds, so we can safely remove it |
| 85 | + break; |
| 86 | + } |
| 87 | + |
| 88 | + // File exists, and the timestamp is less than 30 seconds old, so we need to wait for it to be removed |
| 89 | + await wait(100); |
| 90 | + } |
| 91 | + |
| 92 | + try { |
| 93 | + // write the lockfile to the shared data folder |
| 94 | + await workspace.fs.writeFile(lockFileUri, new Uint8Array(0)); |
| 95 | + } catch (error) { |
| 96 | + Logger.error(error, 'acquireSharedFolderWriteLock'); |
| 97 | + return false; |
| 98 | + } |
42 | 99 |
|
43 |
| -export async function releaseSharedFolderWriteLock(): Promise<boolean> { |
44 |
| - try { |
45 |
| - const lockFileUri = getSharedLockFileUri(); |
46 |
| - await workspace.fs.delete(lockFileUri); |
47 |
| - } catch (error) { |
48 |
| - Logger.error(error, 'releaseSharedFolderWriteLock'); |
49 |
| - return false; |
| 100 | + return true; |
50 | 101 | }
|
51 | 102 |
|
52 |
| - return true; |
53 |
| -} |
| 103 | + async releaseSharedFolderWriteLock(): Promise<boolean> { |
| 104 | + try { |
| 105 | + const lockFileUri = await this.getUri('lockfile'); |
| 106 | + await workspace.fs.delete(lockFileUri); |
| 107 | + } catch (error) { |
| 108 | + Logger.error(error, 'releaseSharedFolderWriteLock'); |
| 109 | + return false; |
| 110 | + } |
54 | 111 |
|
55 |
| -function getSharedLockFileUri() { |
56 |
| - return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'lockfile')); |
57 |
| -} |
| 112 | + return true; |
| 113 | + } |
58 | 114 |
|
59 |
| -export function getSharedRepositoryMappingFileUri() { |
60 |
| - return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'repoMapping.json')); |
61 |
| -} |
| 115 | + async getSharedRepositoryMappingFileUri() { |
| 116 | + return this.getUri('repoMapping.json'); |
| 117 | + } |
62 | 118 |
|
63 |
| -export function getSharedCloudWorkspaceMappingFileUri() { |
64 |
| - return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'cloudWorkspaces.json')); |
65 |
| -} |
| 119 | + async getSharedCloudWorkspaceMappingFileUri() { |
| 120 | + return this.getUri('cloudWorkspaces.json'); |
| 121 | + } |
66 | 122 |
|
67 |
| -export function getSharedLocalWorkspaceMappingFileUri() { |
68 |
| - return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'localWorkspaces.json')); |
| 123 | + async getSharedLocalWorkspaceMappingFileUri() { |
| 124 | + return this.getUri('localWorkspaces.json'); |
| 125 | + } |
69 | 126 | }
|
70 | 127 |
|
| 128 | +// export as a singleton |
| 129 | +const instance = new SharedGKDataFolderMapper(); |
| 130 | +export { instance as SharedGKDataFolderMapper }; |
| 131 | + |
71 | 132 | export function getSharedLegacyLocalWorkspaceMappingFileUri() {
|
72 | 133 | return Uri.file(
|
73 | 134 | path.join(
|
|
0 commit comments