2
2
// Licensed under the MIT License. See the LICENSE.
3
3
4
4
using Microsoft . Data . Sqlite ;
5
+ using Microsoft . Extensions . Logging ;
6
+ using Microsoft . Win32 ;
5
7
using System . IO ;
6
8
using Windows . Storage ;
9
+ using Vanara . Windows . Shell ;
7
10
8
11
namespace Files . App . Utils . Cloud
9
12
{
10
13
/// <summary>
11
- /// Provides an utility for Google Drive Cloud detection.
14
+ /// Provides a utility for Google Drive Cloud detection.
12
15
/// </summary>
13
16
public sealed class GoogleDriveCloudDetector : AbstractCloudDetector
14
17
{
18
+ private static readonly ILogger _logger = Ioc . Default . GetRequiredService < ILogger < App > > ( ) ;
19
+
20
+ private const string _googleDriveRegKeyName = @"Software\Google\DriveFS" ;
21
+ private const string _googleDriveRegValName = "PerAccountPreferences" ;
22
+ private const string _googleDriveRegValPropName = "value" ;
23
+ private const string _googleDriveRegValPropPropName = "mount_point_path" ;
24
+
15
25
protected override async IAsyncEnumerable < ICloudProvider > GetProviders ( )
16
26
{
17
27
// Google Drive's sync database can be in a couple different locations. Go find it.
@@ -56,7 +66,8 @@ await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Path.Combine(a
56
66
var folder = await StorageFolder . GetFolderFromPathAsync ( path ) ;
57
67
string title = reader [ "title" ] ? . ToString ( ) ?? folder . Name ;
58
68
59
- App . AppModel . GoogleDrivePath = path ;
69
+ Debug . WriteLine ( "YIELD RETURNING from `GoogleDriveCloudDetector.GetProviders()` (roots): " ) ;
70
+ Debug . WriteLine ( $ "Name: Google Drive ({ title } ); SyncFolder: { path } ") ;
60
71
61
72
yield return new CloudProvider ( CloudProviders . GoogleDrive )
62
73
{
@@ -65,6 +76,7 @@ await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Path.Combine(a
65
76
} ;
66
77
}
67
78
79
+ var iconFile = await GetGoogleDriveIconFileAsync ( ) ;
68
80
// Google virtual drive
69
81
reader = cmdMedia . ExecuteReader ( ) ;
70
82
@@ -74,13 +86,14 @@ await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Path.Combine(a
74
86
if ( string . IsNullOrWhiteSpace ( path ) )
75
87
continue ;
76
88
89
+ if ( ! AddMyDriveToPathAndValidate ( ref path ) )
90
+ continue ;
91
+
77
92
var folder = await StorageFolder . GetFolderFromPathAsync ( path ) ;
78
93
string title = reader [ "name" ] ? . ToString ( ) ?? folder . Name ;
79
- string iconPath = Path . Combine ( Environment . GetEnvironmentVariable ( "ProgramFiles" ) , "Google" , "Drive File Stream" , "drive_fs.ico" ) ;
80
-
81
- App . AppModel . GoogleDrivePath = path ;
82
94
83
- StorageFile iconFile = await FilesystemTasks . Wrap ( ( ) => StorageFile . GetFileFromPathAsync ( iconPath ) . AsTask ( ) ) ;
95
+ Debug . WriteLine ( "YIELD RETURNING from `GoogleDriveCloudDetector.GetProviders` (media): " ) ;
96
+ Debug . WriteLine ( $ "Name: { title } ; SyncFolder: { path } ") ;
84
97
85
98
yield return new CloudProvider ( CloudProviders . GoogleDrive )
86
99
{
@@ -89,6 +102,190 @@ await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Path.Combine(a
89
102
IconData = iconFile is not null ? await iconFile . ToByteArrayAsync ( ) : null ,
90
103
} ;
91
104
}
105
+
106
+ await Inspect ( database , "SELECT * FROM roots" , "root_preferences db, roots table" ) ;
107
+ await Inspect ( database , "SELECT * FROM media" , "root_preferences db, media table" ) ;
108
+ await Inspect ( database , "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY 1" , "root_preferences db, all tables" ) ;
109
+
110
+ var registryPath = App . AppModel . GoogleDrivePath ;
111
+ if ( ! AddMyDriveToPathAndValidate ( ref registryPath ) )
112
+ yield break ;
113
+ yield return new CloudProvider ( CloudProviders . GoogleDrive )
114
+ {
115
+ Name = "Google Drive" ,
116
+ SyncFolder = registryPath ,
117
+ IconData = iconFile is not null ? await iconFile . ToByteArrayAsync ( ) : null
118
+ } ;
119
+ }
120
+
121
+ private static async Task Inspect ( SqliteConnection database , string sqlCommand , string targetDescription )
122
+ {
123
+ await using var cmdTablesAll = new SqliteCommand ( sqlCommand , database ) ;
124
+ var reader = await cmdTablesAll . ExecuteReaderAsync ( ) ;
125
+ var colNamesList = Enumerable . Range ( 0 , reader . FieldCount ) . Select ( i => reader . GetName ( i ) ) . ToList ( ) ;
126
+
127
+ Debug . WriteLine ( $ "BEGIN LOGGING of { targetDescription } ") ;
128
+
129
+ for ( int rowIdx = 0 ; reader . Read ( ) is not false ; rowIdx ++ )
130
+ {
131
+ var colVals = new object [ reader . FieldCount ] ;
132
+ reader . GetValues ( colVals ) ;
133
+
134
+ colVals . Select ( ( val , colIdx ) => $ "row { rowIdx } : column { colIdx } : { colNamesList [ colIdx ] } : { val } ")
135
+ . ToList ( ) . ForEach ( s => Debug . WriteLine ( s ) ) ;
136
+ }
137
+
138
+ Debug . WriteLine ( $ "END LOGGING of { targetDescription } contents") ;
139
+ }
140
+
141
+ private static JsonDocument ? GetGoogleDriveRegValJson ( )
142
+ {
143
+ // This will be null if the key name is not found.
144
+ using var googleDriveRegKey = Registry . CurrentUser . OpenSubKey ( _googleDriveRegKeyName ) ;
145
+
146
+ if ( googleDriveRegKey is null )
147
+ {
148
+ _logger . LogWarning ( $ "Google Drive registry key for key name '{ _googleDriveRegKeyName } ' not found.") ;
149
+ return null ;
150
+ }
151
+
152
+ var googleDriveRegVal = googleDriveRegKey . GetValue ( _googleDriveRegValName ) ;
153
+
154
+ if ( googleDriveRegVal is null )
155
+ {
156
+ _logger . LogWarning ( $ "Google Drive registry value for value name '{ _googleDriveRegValName } ' not found.") ;
157
+ return null ;
158
+ }
159
+
160
+ JsonDocument ? googleDriveRegValueJson = null ;
161
+ try
162
+ {
163
+ googleDriveRegValueJson = JsonDocument . Parse ( googleDriveRegVal . ToString ( ) ?? "" ) ;
164
+ }
165
+ catch ( JsonException je )
166
+ {
167
+ _logger . LogWarning ( je , $ "Google Drive registry value for value name '{ _googleDriveRegValName } ' could not be parsed as a JsonDocument.") ;
168
+ }
169
+
170
+ return googleDriveRegValueJson ;
171
+ }
172
+
173
+ public static string ? GetRegistryBasePath ( )
174
+ {
175
+ var googleDriveRegValJson = GetGoogleDriveRegValJson ( ) ;
176
+
177
+ if ( googleDriveRegValJson is null )
178
+ return null ;
179
+
180
+ var googleDriveRegValJsonProperty = googleDriveRegValJson
181
+ . RootElement . EnumerateObject ( )
182
+ . FirstOrDefault ( ) ;
183
+
184
+ // A default JsonProperty struct has an "Undefined" Value#ValueKind and throws an
185
+ // error if you try to call EnumerateArray on its Value.
186
+ if ( googleDriveRegValJsonProperty . Value . ValueKind == JsonValueKind . Undefined )
187
+ {
188
+ _logger . LogWarning ( $ "Root element of Google Drive registry value for value name '{ _googleDriveRegValName } ' was empty.") ;
189
+ return null ;
190
+ }
191
+
192
+ Debug . WriteLine ( "REGISTRY LOGGING" ) ;
193
+ Debug . WriteLine ( googleDriveRegValJsonProperty . ToString ( ) ) ;
194
+
195
+ var item = googleDriveRegValJsonProperty . Value . EnumerateArray ( ) . FirstOrDefault ( ) ;
196
+ if ( item . ValueKind == JsonValueKind . Undefined )
197
+ {
198
+ _logger . LogWarning ( $ "Array in the root element of Google Drive registry value for value name '{ _googleDriveRegValName } ' was empty.") ;
199
+ return null ;
200
+ }
201
+
202
+ if ( ! item . TryGetProperty ( _googleDriveRegValPropName , out var googleDriveRegValProp ) )
203
+ {
204
+ _logger . LogWarning ( $ "First element in the Google Drive Registry Root Array did not have property named { _googleDriveRegValPropName } ") ;
205
+ return null ;
206
+ }
207
+
208
+ if ( ! googleDriveRegValProp . TryGetProperty ( _googleDriveRegValPropPropName , out var googleDriveRegValPropProp ) )
209
+ {
210
+ _logger . LogWarning ( $ "Value from { _googleDriveRegValPropName } did not have property named { _googleDriveRegValPropPropName } ") ;
211
+ return null ;
212
+ }
213
+
214
+ var path = googleDriveRegValPropProp . GetString ( ) ;
215
+ if ( path is not null )
216
+ return ConvertDriveLetterToPathAndValidate ( ref path ) ? path : null ;
217
+
218
+ _logger . LogWarning ( $ "Could not get string from value from { _googleDriveRegValPropPropName } ") ;
219
+ return null ;
220
+ }
221
+
222
+ /// <summary>
223
+ /// If Google Drive is mounted as a drive, then the path found in the registry will be
224
+ /// *just* the drive letter (e.g. just "G" as opposed to "G:\"), and therefore must be
225
+ /// reformatted as a valid path.
226
+ /// </summary>
227
+ private static bool ConvertDriveLetterToPathAndValidate ( ref string path )
228
+ {
229
+ if ( path . Length > 1 )
230
+ return ValidatePath ( path ) ;
231
+
232
+ DriveInfo driveInfo ;
233
+ try
234
+ {
235
+ driveInfo = new DriveInfo ( path ) ;
236
+ }
237
+ catch ( ArgumentException e )
238
+ {
239
+ _logger . LogWarning ( e , $ "Could not resolve drive letter '{ path } ' to a valid drive.") ;
240
+ return false ;
241
+ }
242
+
243
+ path = driveInfo . RootDirectory . Name ;
244
+ return true ;
245
+ }
246
+
247
+ private static bool ValidatePath ( string path )
248
+ {
249
+ if ( Directory . Exists ( path ) )
250
+ return true ;
251
+ _logger . LogWarning ( $ "Invalid path: { path } ") ;
252
+ return false ;
253
+ }
254
+
255
+ private static async Task < StorageFile ? > GetGoogleDriveIconFileAsync ( )
256
+ {
257
+ var programFilesEnvVar = Environment . GetEnvironmentVariable ( "ProgramFiles" ) ;
258
+
259
+ if ( programFilesEnvVar is null )
260
+ return null ;
261
+
262
+ var iconPath = Path . Combine ( programFilesEnvVar , "Google" , "Drive File Stream" , "drive_fs.ico" ) ;
263
+
264
+ return await FilesystemTasks . Wrap ( ( ) => StorageFile . GetFileFromPathAsync ( iconPath ) . AsTask ( ) ) ;
265
+ }
266
+
267
+ private static bool AddMyDriveToPathAndValidate ( ref string path )
268
+ {
269
+ // If `path` contains a shortcut named "My Drive", store its target in `shellFolderBaseFirst`.
270
+ // This happens when "My Drive syncing options" is set to "Mirror files".
271
+ // TODO: Avoid to use Vanara (#15000)
272
+ using var rootFolder = ShellFolderExtensions . GetShellItemFromPathOrPIDL ( path ) as ShellFolder ;
273
+ var myDriveFolder = Environment . ExpandEnvironmentVariables ( (
274
+ rootFolder ? . FirstOrDefault ( si =>
275
+ si . Name ? . Equals ( "My Drive" ) ?? false ) as ShellLink ) ? . TargetPath
276
+ ?? string . Empty ) ;
277
+
278
+ Debug . WriteLine ( "SHELL FOLDER LOGGING" ) ;
279
+ rootFolder ? . ForEach ( si => Debug . WriteLine ( si . Name ) ) ;
280
+
281
+ if ( ! string . IsNullOrEmpty ( myDriveFolder ) )
282
+ {
283
+ path = myDriveFolder ;
284
+ return true ;
285
+ }
286
+
287
+ path = Path . Combine ( path , "My Drive" ) ;
288
+ return ValidatePath ( path ) ;
92
289
}
93
290
}
94
291
}
0 commit comments