1+ /*---------------------------------------------------------------------------------------------
2+ * Copyright (c) Microsoft Corporation. All rights reserved.
3+ * Licensed under the MIT License. See License.txt in the project root for license information.
4+ *--------------------------------------------------------------------------------------------*/
5+
6+ 'use strict' ;
7+
8+ import { IStorage , StorageService } from "vs/platform/storage/common/storageService" ;
9+ import { endsWith , startsWith , rtrim } from "vs/base/common/strings" ;
10+ import URI from "vs/base/common/uri" ;
11+ import { IWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces" ;
12+
13+ /**
14+ * We currently store local storage with the following format:
15+ *
16+ * [Global]
17+ * storage://global/<key>
18+ *
19+ * [Workspace]
20+ * storage://workspace/<folder>/<key>
21+ * storage://workspace/empty:<id>/<key>
22+ * storage://workspace/root:<id>/<key>
23+ *
24+ * <folder>
25+ * macOS/Linux: /some/folder/path
26+ * Windows: c%3A/Users/name/folder (normal path)
27+ * file://localhost/c%24/name/folder (unc path)
28+ *
29+ * [no workspace]
30+ * storage://workspace/__$noWorkspace__<key>
31+ * => no longer being used (used for empty workspaces previously)
32+ */
33+
34+ const EMPTY_WORKSPACE_PREFIX = `${ StorageService . COMMON_PREFIX } workspace/empty:` ;
35+ const MULTI_ROOT_WORKSPACE_PREFIX = `${ StorageService . COMMON_PREFIX } workspace/root:` ;
36+
37+ export type StorageObject = { [ key : string ] : string } ;
38+
39+ export interface IParsedStorage {
40+ global : Map < string , string > ;
41+ multiRoot : Map < string , StorageObject > ;
42+ folder : Map < string , StorageObject > ;
43+ empty : Map < string , StorageObject > ;
44+ }
45+
46+ /**
47+ * Parses the local storage implementation into global, multi root, folder and empty storage.
48+ */
49+ export function parseStorage ( storage : IStorage ) : IParsedStorage {
50+ const globalStorage = new Map < string , string > ( ) ;
51+ const folderWorkspacesStorage = new Map < string /* workspace file resource */ , StorageObject > ( ) ;
52+ const emptyWorkspacesStorage = new Map < string /* empty workspace id */ , StorageObject > ( ) ;
53+ const multiRootWorkspacesStorage = new Map < string /* multi root workspace id */ , StorageObject > ( ) ;
54+
55+ const workspaces : { prefix : string ; resource : string ; } [ ] = [ ] ;
56+ for ( let i = 0 ; i < storage . length ; i ++ ) {
57+ const key = storage . key ( i ) ;
58+
59+ // Workspace Storage (storage://workspace/)
60+ if ( startsWith ( key , StorageService . WORKSPACE_PREFIX ) ) {
61+
62+ // We are looking for key: storage://workspace/<folder>/workspaceIdentifier to be able to find all folder
63+ // paths that are known to the storage. is the only way how to parse all folder paths known in storage.
64+ if ( endsWith ( key , StorageService . WORKSPACE_IDENTIFIER ) ) {
65+
66+ // storage://workspace/<folder>/workspaceIdentifier => <folder>/
67+ let workspace = key . substring ( StorageService . WORKSPACE_PREFIX . length , key . length - StorageService . WORKSPACE_IDENTIFIER . length ) ;
68+
69+ // macOS/Unix: Users/name/folder/
70+ // Windows: c%3A/Users/name/folder/
71+ if ( ! startsWith ( workspace , 'file:' ) ) {
72+ workspace = `file:///${ rtrim ( workspace , '/' ) } ` ;
73+ }
74+
75+ // Windows UNC path: file://localhost/c%3A/Users/name/folder/
76+ else {
77+ workspace = rtrim ( workspace , '/' ) ;
78+ }
79+
80+ // storage://workspace/<folder>/workspaceIdentifier => storage://workspace/<folder>/
81+ const prefix = key . substr ( 0 , key . length - StorageService . WORKSPACE_IDENTIFIER . length ) ;
82+ workspaces . push ( { prefix, resource : workspace } ) ;
83+ }
84+
85+ // Empty workspace key: storage://workspace/empty:<id>/<key>
86+ else if ( startsWith ( key , EMPTY_WORKSPACE_PREFIX ) ) {
87+
88+ // storage://workspace/empty:<id>/<key> => <id>
89+ const emptyWorkspaceId = key . substring ( EMPTY_WORKSPACE_PREFIX . length , key . indexOf ( '/' , EMPTY_WORKSPACE_PREFIX . length ) ) ;
90+ const emptyWorkspaceResource = URI . from ( { path : emptyWorkspaceId , scheme : 'empty' } ) . toString ( ) ;
91+
92+ let emptyWorkspaceStorage = emptyWorkspacesStorage . get ( emptyWorkspaceResource ) ;
93+ if ( ! emptyWorkspaceStorage ) {
94+ emptyWorkspaceStorage = Object . create ( null ) ;
95+ emptyWorkspacesStorage . set ( emptyWorkspaceResource , emptyWorkspaceStorage ) ;
96+ }
97+
98+ // storage://workspace/empty:<id>/someKey => someKey
99+ const storageKey = key . substr ( EMPTY_WORKSPACE_PREFIX . length + emptyWorkspaceId . length + 1 /* trailing / */ ) ;
100+
101+ emptyWorkspaceStorage [ storageKey ] = storage . getItem ( key ) ;
102+ }
103+
104+ // Multi root workspace key: storage://workspace/root:<id>/<key>
105+ else if ( startsWith ( key , MULTI_ROOT_WORKSPACE_PREFIX ) ) {
106+
107+ // storage://workspace/root:<id>/<key> => <id>
108+ const multiRootWorkspaceId = key . substring ( MULTI_ROOT_WORKSPACE_PREFIX . length , key . indexOf ( '/' , MULTI_ROOT_WORKSPACE_PREFIX . length ) ) ;
109+ const multiRootWorkspaceResource = URI . from ( { path : multiRootWorkspaceId , scheme : 'root' } ) . toString ( ) ;
110+
111+ let multiRootWorkspaceStorage = multiRootWorkspacesStorage . get ( multiRootWorkspaceResource ) ;
112+ if ( ! multiRootWorkspaceStorage ) {
113+ multiRootWorkspaceStorage = Object . create ( null ) ;
114+ multiRootWorkspacesStorage . set ( multiRootWorkspaceResource , multiRootWorkspaceStorage ) ;
115+ }
116+
117+ // storage://workspace/root:<id>/someKey => someKey
118+ const storageKey = key . substr ( MULTI_ROOT_WORKSPACE_PREFIX . length + multiRootWorkspaceId . length + 1 /* trailing / */ ) ;
119+
120+ multiRootWorkspaceStorage [ storageKey ] = storage . getItem ( key ) ;
121+ }
122+ }
123+
124+ // Global Storage (storage://global)
125+ else if ( startsWith ( key , StorageService . GLOBAL_PREFIX ) ) {
126+
127+ // storage://global/someKey => someKey
128+ const globalStorageKey = key . substr ( StorageService . GLOBAL_PREFIX . length ) ;
129+ if ( startsWith ( globalStorageKey , StorageService . COMMON_PREFIX ) ) {
130+ continue ; // filter out faulty keys that have the form storage://something/storage://
131+ }
132+
133+ globalStorage . set ( globalStorageKey , storage . getItem ( key ) ) ;
134+ }
135+ }
136+
137+ // With all the folder paths known we can now extract storage for each path. We have to go through all workspaces
138+ // from the longest path first to reliably extract the storage. The reason is that one folder path can be a parent
139+ // of another folder path and as such a simple indexOf check is not enough.
140+ const workspacesByLength = workspaces . sort ( ( w1 , w2 ) => w1 . prefix . length >= w2 . prefix . length ? - 1 : 1 ) ;
141+ const handledKeys = new Map < string , boolean > ( ) ;
142+ workspacesByLength . forEach ( workspace => {
143+ for ( let i = 0 ; i < storage . length ; i ++ ) {
144+ const key = storage . key ( i ) ;
145+
146+ if ( handledKeys . has ( key ) || ! startsWith ( key , workspace . prefix ) ) {
147+ continue ; // not part of workspace prefix or already handled
148+ }
149+
150+ handledKeys . set ( key , true ) ;
151+
152+ let folderWorkspaceStorage = folderWorkspacesStorage . get ( workspace . resource ) ;
153+ if ( ! folderWorkspaceStorage ) {
154+ folderWorkspaceStorage = Object . create ( null ) ;
155+ folderWorkspacesStorage . set ( workspace . resource , folderWorkspaceStorage ) ;
156+ }
157+
158+ // storage://workspace/<folder>/someKey => someKey
159+ const storageKey = key . substr ( workspace . prefix . length ) ;
160+
161+ folderWorkspaceStorage [ storageKey ] = storage . getItem ( key ) ;
162+ }
163+ } ) ;
164+
165+ return {
166+ global : globalStorage ,
167+ multiRoot : multiRootWorkspacesStorage ,
168+ folder : folderWorkspacesStorage ,
169+ empty : emptyWorkspacesStorage
170+ } ;
171+ }
172+
173+ export function migrateStorageToMultiRootWorkspace ( fromWorkspaceId : string , toWorkspaceId : IWorkspaceIdentifier , storage : IStorage ) : void {
174+ const parsed = parseStorage ( storage ) ;
175+
176+ const newStorageKey = URI . from ( { path : toWorkspaceId . id , scheme : 'root' } ) . toString ( ) ;
177+
178+ // Find in which location the workspace storage is to be migrated rom
179+ let storageForWorkspace : StorageObject ;
180+ if ( parsed . multiRoot . has ( fromWorkspaceId ) ) {
181+ storageForWorkspace = parsed . multiRoot . get ( fromWorkspaceId ) ;
182+ } else if ( parsed . empty . has ( fromWorkspaceId ) ) {
183+ storageForWorkspace = parsed . empty . get ( fromWorkspaceId ) ;
184+ } else if ( parsed . folder . has ( fromWorkspaceId ) ) {
185+ storageForWorkspace = parsed . folder . get ( fromWorkspaceId ) ;
186+ }
187+
188+ // Migrate existing storage to new workspace id
189+ if ( storageForWorkspace ) {
190+ Object . keys ( storageForWorkspace ) . forEach ( key => {
191+ if ( key === StorageService . WORKSPACE_IDENTIFIER ) {
192+ return ; // make sure to never migrate the workspace identifier
193+ }
194+
195+ storage . setItem ( `${ StorageService . WORKSPACE_PREFIX } ${ newStorageKey } /${ key } ` , storageForWorkspace [ key ] ) ;
196+ } ) ;
197+ }
198+ }
0 commit comments