Skip to content

Code: Implement inter-process synchronization for LiteDB #10250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 110 additions & 18 deletions src/Files.App/Filesystem/FileTagsHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
using Common;
using Files.App.Filesystem.StorageItems;
using Files.App.Helpers;
using Files.App.Shell;
using Files.Shared.Extensions;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
using Windows.Storage;
using Windows.Storage.FileProperties;
using IO = System.IO;
Expand All @@ -14,41 +19,128 @@ public static class FileTagsHelper
{
public static string FileTagsDbPath => IO.Path.Combine(ApplicationData.Current.LocalFolder.Path, "filetags.db");

private static readonly Lazy<FileTagsDb> dbInstance = new(() => new FileTagsDb(FileTagsDbPath, true));
public static FileTagsDb DbInstance => dbInstance.Value;
public static FileTagsDb GetDbInstance()
{
return new FileTagsDb(FileTagsDbPath);
}

public static string[] ReadFileTag(string filePath)
{
var tagString = NativeFileOperationsHelper.ReadStringFromFile($"{filePath}:files");
return tagString?.Split(',');
using var hStream = Kernel32.CreateFile($"{filePath}:files",
Kernel32.FileAccess.GENERIC_READ, 0, null, FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
if (hStream.IsInvalid)
{
return null;
}
var bytes = new byte[4096];
var ret = Kernel32.ReadFile(hStream, bytes, (uint)bytes.Length, out var read, IntPtr.Zero);
if (!ret)
{
return null;
}
var tagString = System.Text.Encoding.UTF8.GetString(bytes, 0, (int)read);
return tagString.Split(',');
}

public static void WriteFileTag(string filePath, string[] tag)
public static bool WriteFileTag(string filePath, string[] tag)
{
var isDateOk = NativeFileOperationsHelper.GetFileDateModified(filePath, out var dateModified); // Backup date modified
var isReadOnly = NativeFileOperationsHelper.HasFileAttribute(filePath, IO.FileAttributes.ReadOnly);
if (isReadOnly) // Unset read-only attribute (#7534)
var dateOk = GetFileDateModified(filePath, out var dateModified); // Backup date modified
bool result = false;
if (tag is null || !tag.Any())
{
NativeFileOperationsHelper.UnsetFileAttribute(filePath, IO.FileAttributes.ReadOnly);
result = Kernel32.DeleteFile($"{filePath}:files");
}
if (tag is null || !tag.Any())
else
{
NativeFileOperationsHelper.DeleteFileFromApp($"{filePath}:files");
using var hStream = Kernel32.CreateFile($"{filePath}:files",
Kernel32.FileAccess.GENERIC_WRITE, 0, null, FileMode.Create, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
if (hStream.IsInvalid)
{
return false;
}
byte[] buff = System.Text.Encoding.UTF8.GetBytes(string.Join(',', tag));
result = Kernel32.WriteFile(hStream, buff, (uint)buff.Length, out var written, IntPtr.Zero);
}
else if (ReadFileTag(filePath) is not string[] arr || !tag.SequenceEqual(arr))
if (dateOk)
{
NativeFileOperationsHelper.WriteStringToFile($"{filePath}:files", string.Join(',', tag));
SetFileDateModified(filePath, dateModified); // Restore date modified
}
if (isReadOnly) // Restore read-only attribute (#7534)
return result;
}

public static ulong? GetFileFRN(string filePath)
{
//using var si = new ShellItem(filePath);
//return (ulong?)si.Properties["System.FileFRN"]; // Leaves open file handles
using var hFile = Kernel32.CreateFile(filePath, Kernel32.FileAccess.GENERIC_READ, FileShare.ReadWrite, null, FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS);
if (hFile.IsInvalid)
{
NativeFileOperationsHelper.SetFileAttribute(filePath, IO.FileAttributes.ReadOnly);
return null;
}
if (isDateOk)
ulong? frn = null;
SafetyExtensions.IgnoreExceptions(() =>
{
NativeFileOperationsHelper.SetFileDateModified(filePath, dateModified); // Restore date modified
var fileID = Kernel32.GetFileInformationByHandleEx<Kernel32.FILE_ID_INFO>(hFile, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileIdInfo);
frn = BitConverter.ToUInt64(fileID.FileId.Identifier, 0);
});
return frn;
}

public static void UpdateTagsDb()
{
using var dbInstance = GetDbInstance();
foreach (var file in dbInstance.GetAll())
{
var pathFromFrn = Win32API.PathFromFileId(file.Frn ?? 0, file.FilePath);
if (pathFromFrn != null)
{
// Frn is valid, update file path
var tag = ReadFileTag(pathFromFrn.Replace(@"\\?\", "", StringComparison.Ordinal));
if (tag is not null && tag.Any())
{
dbInstance.UpdateTag(file.Frn ?? 0, null, pathFromFrn.Replace(@"\\?\", "", StringComparison.Ordinal));
dbInstance.SetTags(pathFromFrn.Replace(@"\\?\", "", StringComparison.Ordinal), file.Frn, tag);
}
else
{
dbInstance.SetTags(null, file.Frn, null);
}
}
else
{
var tag = ReadFileTag(file.FilePath);
if (tag is not null && tag.Any())
{
if (!SafetyExtensions.IgnoreExceptions(() =>
{
var frn = GetFileFRN(file.FilePath);
dbInstance.UpdateTag(file.FilePath, frn, null);
dbInstance.SetTags(file.FilePath, (ulong?)frn, tag);
}, App.Logger))
{
dbInstance.SetTags(file.FilePath, null, null);
}
}
else
{
dbInstance.SetTags(file.FilePath, null, null);
}
}
}
}

private static bool GetFileDateModified(string filePath, out FILETIME dateModified)
{
using var hFile = Kernel32.CreateFile(filePath, Kernel32.FileAccess.GENERIC_READ, FileShare.Read, null, FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS);
return Kernel32.GetFileTime(hFile, out _, out _, out dateModified);
}

private static bool SetFileDateModified(string filePath, FILETIME dateModified)
{
using var hFile = Kernel32.CreateFile(filePath, Kernel32.FileAccess.FILE_WRITE_ATTRIBUTES, FileShare.None, null, FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS);
return Kernel32.SetFileTime(hFile, new(), new(), dateModified);
}

public static Task<ulong?> GetFileFRN(IStorageItem item)
{
return item switch
Expand Down
5 changes: 4 additions & 1 deletion src/Files.App/Filesystem/ListedItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ public string[] FileTags
{
if (SetProperty(ref fileTags, value))
{
FileTagsHelper.DbInstance.SetTags(ItemPath, FileFRN, value);
using (var dbInstance = FileTagsHelper.GetDbInstance())
{
dbInstance.SetTags(ItemPath, FileFRN, value);
}
FileTagsHelper.WriteFileTag(ItemPath, value);
OnPropertyChanged(nameof(FileTagsUI));
}
Expand Down
9 changes: 6 additions & 3 deletions src/Files.App/Filesystem/Search/FolderSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,12 @@ private async Task SearchTagsAsync(string folder, IList<ListedItem> results, Can
{
return;
}

var matches = FileTagsHelper.DbInstance.GetAllUnderPath(folder)
.Where(x => tags.All(x.Tags.Contains));
IEnumerable<Common.FileTagsDb.TaggedFile>? matches;
using (var dbInstance = FileTagsHelper.GetDbInstance())
{
matches = dbInstance.GetAllUnderPath(folder)
.Where(x => tags.All(x.Tags.Contains));
}

foreach (var match in matches)
{
Expand Down
13 changes: 7 additions & 6 deletions src/Files.App/Helpers/FileOperationsHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Windows.Forms;
using Common;
using Files.App.DataModels;
using Files.App.Filesystem;
using Files.App.Filesystem.Permissions;
using Files.App.Shell;
using Files.Shared;
Expand All @@ -28,7 +29,6 @@ namespace Files.App.Helpers
{
public class FileOperationsHelpers
{
private static FileTagsDb dbInstance = new(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "filetags.db"));
private static readonly ProgressHandler progressHandler = new();

public static long? GetFileHandle(string filePath, bool readWrite, int processId)
Expand Down Expand Up @@ -729,6 +729,7 @@ public static bool SetCompatOptions(string filePath, string options)

private static void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e, string operationType)
{
using var dbInstance = FileTagsHelper.GetDbInstance();
if (e.Result.Succeeded)
{
var sourcePath = e.SourceItem.GetParsingPath();
Expand All @@ -752,16 +753,16 @@ private static void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e,
{
var tag = dbInstance.GetTags(sourcePath);

dbInstance.SetTags(destination, FileTagsHelpers.GetFileFRN(destination), tag); // copy tag to new files
dbInstance.SetTags(destination, FileTagsHelper.GetFileFRN(destination), tag); // copy tag to new files
using var si = new ShellItem(destination);
if (si.IsFolder) // File tag is not copied automatically for folders
{
FileTagsHelpers.WriteFileTag(destination, tag);
FileTagsHelper.WriteFileTag(destination, tag);
}
}
else
{
dbInstance.UpdateTag(sourcePath, FileTagsHelpers.GetFileFRN(destination), destination); // move tag to new files
dbInstance.UpdateTag(sourcePath, FileTagsHelper.GetFileFRN(destination), destination); // move tag to new files
}
}, App.Logger);
}
Expand All @@ -781,7 +782,7 @@ private static void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e,
SafetyExtensions.IgnoreExceptions(() =>
{
var subPath = t.FilePath.Replace(sourcePath, destination, StringComparison.Ordinal);
dbInstance.SetTags(subPath, FileTagsHelpers.GetFileFRN(subPath), t.Tags);
dbInstance.SetTags(subPath, FileTagsHelper.GetFileFRN(subPath), t.Tags);
}, App.Logger);
});
}
Expand All @@ -792,7 +793,7 @@ private static void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e,
SafetyExtensions.IgnoreExceptions(() =>
{
var subPath = t.FilePath.Replace(sourcePath, destination, StringComparison.Ordinal);
dbInstance.UpdateTag(t.FilePath, FileTagsHelpers.GetFileFRN(subPath), subPath);
dbInstance.UpdateTag(t.FilePath, FileTagsHelper.GetFileFRN(subPath), subPath);
}, App.Logger);
});
}
Expand Down
132 changes: 0 additions & 132 deletions src/Files.App/Helpers/FileTagsHelpers.cs

This file was deleted.

Loading