Skip to content

Commit 9ecf257

Browse files
authored
Fix: Fixed an issue where Google Drive wasn't displayed when mounted as a folder (#15771)
1 parent ee9532f commit 9ecf257

File tree

2 files changed

+213
-11
lines changed

2 files changed

+213
-11
lines changed

src/Files.App/Services/Storage/StorageDevicesService.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,20 @@ public IStorageDeviceWatcher CreateWatcher()
1919
public async IAsyncEnumerable<ILocatableFolder> GetDrivesAsync()
2020
{
2121
var list = DriveInfo.GetDrives();
22-
var googleDrivePath = App.AppModel.GoogleDrivePath;
2322
var pCloudDrivePath = App.AppModel.PCloudDrivePath;
2423

24+
var sw = Stopwatch.StartNew();
25+
var googleDrivePath = GoogleDriveCloudDetector.GetRegistryBasePath();
26+
sw.Stop();
27+
Debug.WriteLine($"In RemovableDrivesService: Time elapsed for registry check: {sw.Elapsed}");
28+
App.AppModel.GoogleDrivePath = googleDrivePath ?? string.Empty;
29+
2530
foreach (var drive in list)
2631
{
32+
// We don't want cloud drives to appear in a plain "Drives" section.
33+
if (drive.Name.Equals(googleDrivePath) || drive.Name.Equals(pCloudDrivePath))
34+
continue;
35+
2736
var res = await FilesystemTasks.Wrap(() => StorageFolder.GetFolderFromPathAsync(drive.Name).AsTask());
2837
if (res.ErrorCode is FileSystemStatusCode.Unauthorized)
2938
{
@@ -43,10 +52,6 @@ public async IAsyncEnumerable<ILocatableFolder> GetDrivesAsync()
4352
var label = DriveHelpers.GetExtendedDriveLabel(drive);
4453
var driveItem = await DriveItem.CreateFromPropertiesAsync(res.Result, drive.Name.TrimEnd('\\'), label, type, thumbnail);
4554

46-
// Don't add here because Google Drive is already displayed under cloud drives
47-
if (drive.Name == googleDrivePath || drive.Name == pCloudDrivePath)
48-
continue;
49-
5055
App.Logger.LogInformation($"Drive added: {driveItem.Path}, {driveItem.Type}");
5156

5257
yield return driveItem;

src/Files.App/Utils/Cloud/Detector/GoogleDriveCloudDetector.cs

Lines changed: 203 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,26 @@
22
// Licensed under the MIT License. See the LICENSE.
33

44
using Microsoft.Data.Sqlite;
5+
using Microsoft.Extensions.Logging;
6+
using Microsoft.Win32;
57
using System.IO;
68
using Windows.Storage;
9+
using Vanara.Windows.Shell;
710

811
namespace Files.App.Utils.Cloud
912
{
1013
/// <summary>
11-
/// Provides an utility for Google Drive Cloud detection.
14+
/// Provides a utility for Google Drive Cloud detection.
1215
/// </summary>
1316
public sealed class GoogleDriveCloudDetector : AbstractCloudDetector
1417
{
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+
1525
protected override async IAsyncEnumerable<ICloudProvider> GetProviders()
1626
{
1727
// 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
5666
var folder = await StorageFolder.GetFolderFromPathAsync(path);
5767
string title = reader["title"]?.ToString() ?? folder.Name;
5868

59-
App.AppModel.GoogleDrivePath = path;
69+
Debug.WriteLine("YIELD RETURNING from `GoogleDriveCloudDetector.GetProviders()` (roots): ");
70+
Debug.WriteLine($"Name: Google Drive ({title}); SyncFolder: {path}");
6071

6172
yield return new CloudProvider(CloudProviders.GoogleDrive)
6273
{
@@ -65,6 +76,7 @@ await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Path.Combine(a
6576
};
6677
}
6778

79+
var iconFile = await GetGoogleDriveIconFileAsync();
6880
// Google virtual drive
6981
reader = cmdMedia.ExecuteReader();
7082

@@ -74,13 +86,14 @@ await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Path.Combine(a
7486
if (string.IsNullOrWhiteSpace(path))
7587
continue;
7688

89+
if (!AddMyDriveToPathAndValidate(ref path))
90+
continue;
91+
7792
var folder = await StorageFolder.GetFolderFromPathAsync(path);
7893
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;
8294

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}");
8497

8598
yield return new CloudProvider(CloudProviders.GoogleDrive)
8699
{
@@ -89,6 +102,190 @@ await FilesystemTasks.Wrap(() => StorageFile.GetFileFromPathAsync(Path.Combine(a
89102
IconData = iconFile is not null ? await iconFile.ToByteArrayAsync() : null,
90103
};
91104
}
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);
92289
}
93290
}
94291
}

0 commit comments

Comments
 (0)