Skip to content

Commit

Permalink
Prepared Reference Files for overhaul
Browse files Browse the repository at this point in the history
  • Loading branch information
d2dyno1 committed Aug 30, 2021
1 parent 1eecf56 commit b8f212b
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 44 deletions.
4 changes: 2 additions & 2 deletions ClipboardCanvas/CanavsPasteModels/BasePasteModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ protected async Task<SafeWrapperResult> SaveDataToFileInternal()
if (IsContentAsReference && await sourceItem == null)
{
// We need to update the reference file because the one we created is empty
ReferenceFile referenceFile = await ReferenceFile.GetReferenceFile(associatedFile);
return await referenceFile.UpdateReference(_pastedItem.Path);
ReferenceFile referenceFile = await ReferenceFile.CreateReferenceFileFromFile(associatedFile, _pastedItem);
return referenceFile.LastError;
}
else
{
Expand Down
12 changes: 12 additions & 0 deletions ClipboardCanvas/Extensions/LinqExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ namespace ClipboardCanvas.Extensions
{
public static class LinqExtensions
{
/// <summary>
/// Determines whether <paramref name="enumerable"/> is empty or not.
/// <br/><br/>
/// Remarks:
/// <br/>
/// This function is faster than enumerable.Count == 0 since it'll only iterate one element instead of all elements.
/// <br/>
/// This function is null-safe.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="enumerable"></param>
/// <returns></returns>
public static bool IsEmpty<T>(this IEnumerable<T> enumerable) => enumerable == null || !enumerable.Any();

public static bool CheckEveryNotNull<T>(this IEnumerable<T> enumerable) => !(enumerable == null || enumerable.Any((item) => item.IsNull()));
Expand Down
6 changes: 5 additions & 1 deletion ClipboardCanvas/Extensions/SafetyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.Win32.SafeHandles;
using System;
using System.Diagnostics;

namespace ClipboardCanvas.Extensions
Expand All @@ -24,5 +25,8 @@ public static T PreventNull<T>(this T element, T defaultValue, bool throwOnNullD
return element;
}
}

public static SafeFileHandle ToSafeFileHandle(this IntPtr unsafeHandle) =>
new SafeFileHandle(unsafeHandle, true);
}
}
107 changes: 95 additions & 12 deletions ClipboardCanvas/ReferenceItems/ReferenceFile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System;
using Newtonsoft.Json;
using System.IO;
using System.Threading.Tasks;
using Windows.Storage;
Expand All @@ -7,7 +8,7 @@
using ClipboardCanvas.Exceptions;
using ClipboardCanvas.Helpers.Filesystem;
using ClipboardCanvas.Helpers.SafetyHelpers;
using System;
using ClipboardCanvas.UnsafeNative;

namespace ClipboardCanvas.ReferenceItems
{
Expand All @@ -30,12 +31,57 @@ private ReferenceFile(StorageFile innerFile, IStorageItem referencedItem)
this.ReferencedItem = referencedItem;
}

public async Task<SafeWrapperResult> UpdateReference(string newPath)
/// <summary>
/// Updates the underlying file and fileData
/// </summary>
/// <param name="newPath"></param>
/// <returns></returns>
private async Task<SafeWrapperResult> UpdateReference(string newPath, bool regenerateReferenceItem)
{
_referenceFileData = new ReferenceFileData(newPath);
if (!StorageHelpers.Existsh(newPath))
{
return new SafeWrapperResult(OperationErrorCode.NotFound, new FileNotFoundException(), "File does not exist.");
}

long fileId = UnsafeNativeHelpers.GetFileId(newPath);
bool isRepairable = fileId != -1;

_referenceFileData = new ReferenceFileData(newPath, fileId, isRepairable);
string serialized = JsonConvert.SerializeObject(_referenceFileData, Formatting.Indented);

return await FilesystemOperations.WriteFileText(_innerReferenceFile, serialized);
SafeWrapperResult writeFileTextResult = await FilesystemOperations.WriteFileText(_innerReferenceFile, serialized);
if (!writeFileTextResult)
{
return writeFileTextResult;
}

if (regenerateReferenceItem)
{
SafeWrapper<IStorageItem> referencedItem = await StorageHelpers.ToStorageItemWithError<IStorageItem>(newPath);
this.ReferencedItem = referencedItem.Result;

return referencedItem;
}

return SafeWrapperResult.SUCCESS;
}

public static async Task<ReferenceFile> CreateReferenceFileFromFile(StorageFile emptyReferenceFile, IStorageItem referencedItem)
{
if (!FileHelpers.IsPathEqualExtension(emptyReferenceFile.Path, Constants.FileSystem.REFERENCE_FILE_EXTENSION))
{
return new ReferenceFile(emptyReferenceFile, null)
{
LastError = new SafeWrapperResult(OperationErrorCode.InvalidArgument, new ArgumentException(), "Empty file uses invalid Reference File extension.")
};
}

ReferenceFile referenceFile = new ReferenceFile(emptyReferenceFile, referencedItem);

SafeWrapperResult result = await referenceFile.UpdateReference(referencedItem.Path, false);
referenceFile.LastError = result;

return referenceFile;
}

private static async Task<SafeWrapper<ReferenceFileData>> ReadData(StorageFile referenceFile)
Expand All @@ -50,26 +96,48 @@ private static async Task<SafeWrapper<ReferenceFileData>> ReadData(StorageFile r
return (referenceFileData, SafeWrapperResult.SUCCESS);
}

public static async Task<ReferenceFile> GetReferenceFile(StorageFile referenceFile)
public static async Task<ReferenceFile> GetReferenceFile(StorageFile file)
{
// The file is not a Reference File
if (!IsReferenceFile(referenceFile))
if (!IsReferenceFile(file))
{
return null;
}

// The file does not exist
if (!StorageHelpers.Existsh(referenceFile.Path))
// The referenceFile does not exist
if (!StorageHelpers.Existsh(file.Path))
{
return new ReferenceFile(referenceFile, null)
return new ReferenceFile(file, null)
{
LastError = new SafeWrapperResult(OperationErrorCode.NotFound, new FileNotFoundException(), "Couldn't resolve item associated with path.")
};
}

SafeWrapper<ReferenceFileData> referenceFileData = await ReadData(referenceFile);
SafeWrapper<ReferenceFileData> referenceFileData = await ReadData(file);

ReferenceFile referenceFile = await GetFile(file, referenceFileData);
if (referenceFile.LastError)
{
if (!referenceFileData.Result.isRepairable) // Bad FileId but the path is correct
{
// Repair the FileId
await referenceFile.UpdateReference(referenceFileData.Result.path, false);
}
}
else
{
if (referenceFile.LastError == OperationErrorCode.InvalidArgument || referenceFile.LastError == OperationErrorCode.NotFound)
{
if (referenceFileData.Result?.isRepairable ?? false)
{
// Repair it
SafeWrapperResult result = await RepairReferenceFile(referenceFile, referenceFileData);
referenceFile.LastError = result;
}
}
}

return await GetFile(referenceFile, referenceFileData);
return referenceFile;
}

private static async Task<ReferenceFile> GetFile(StorageFile referenceFile, SafeWrapper<ReferenceFileData> referenceFileData)
Expand Down Expand Up @@ -116,6 +184,21 @@ private static async Task<ReferenceFile> GetFile(StorageFile referenceFile, Safe
};
}

private static async Task<SafeWrapperResult> RepairReferenceFile(ReferenceFile referenceFile, ReferenceFileData referenceFileData)
{
if (referenceFileData.fileId == -1)
{
return referenceFile.LastError;
}

// Get path from FileId...

// TODO: Implement that when it becomes possible

// for now, return the error
return referenceFile.LastError;
}

public static bool IsReferenceFile(StorageFile file)
{
return FileHelpers.IsPathEqualExtension(file?.Path, Constants.FileSystem.REFERENCE_FILE_EXTENSION);
Expand Down
21 changes: 7 additions & 14 deletions ClipboardCanvas/ReferenceItems/ReferenceFileData.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace ClipboardCanvas.ReferenceItems
{
[Serializable]
public sealed class ReferenceFileData
{
[JsonRequired]
public readonly string path;

[JsonConstructor]
public ReferenceFileData(string path)
{
this.path = path;
}
public readonly long fileId;

public Dictionary<string, object> ToRawData()
{
Dictionary<string, object> rawData = new Dictionary<string, object>();
public readonly bool isRepairable;

rawData.Add(nameof(path), path);

return rawData;
public ReferenceFileData(string path, long fileId, bool isRepairable)
{
this.path = path;
this.fileId = fileId;
this.isRepairable = isRepairable;
}
}
}
8 changes: 8 additions & 0 deletions ClipboardCanvas/UnsafeNative/UnsafeNativeApis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public static extern bool GetFileAttributesExFromApp(
GET_FILEEX_INFO_LEVELS fInfoLevelId,
out WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);

[DllImport("api-ms-win-core-file-l2-1-1.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetFileInformationByHandleEx(
IntPtr hFile,
FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
out BY_HANDLE_FILE_INFORMATION lpFileInformation,
uint dwBufferSize);

[DllImport("api-ms-win-core-handle-l1-1-0.dll")]
public static extern bool CloseHandle(IntPtr hObject);
}
Expand Down
41 changes: 41 additions & 0 deletions ClipboardCanvas/UnsafeNative/UnsafeNativeDataModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ public struct WIN32_FILE_ATTRIBUTE_DATA
public uint nFileSizeLow;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
}

public enum GET_FILEEX_INFO_LEVELS
{
GetFileExInfoStandard,
Expand Down Expand Up @@ -51,6 +66,32 @@ public enum File_Attributes : uint
FirstPipeInstance = 0x00080000
}

public enum FILE_INFO_BY_HANDLE_CLASS
{
FileBasicInfo = 0,
FileStandardInfo = 1,
FileNameInfo = 2,
FileRenameInfo = 3,
FileDispositionInfo = 4,
FileAllocationInfo = 5,
FileEndOfFileInfo = 6,
FileStreamInfo = 7,
FileCompressionInfo = 8,
FileAttributeTagInfo = 9,
FileIdBothDirectoryInfo = 10,
FileIdBothDirectoryRestartInfo = 11,
FileIoPriorityHintInfo = 12,
FileRemoteProtocolInfo = 13,
FileFullDirectoryInfo = 14,
FileFullDirectoryRestartInfo = 15,
FileStorageInfo = 16,
FileAlignmentInfo = 17,
FileIdInfo = 18,
FileIdExtdDirectoryInfo = 19,
FileIdExtdDirectoryRestartInfo = 20,
MaximumFileInfoByHandlesClass = 21
}

public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;

Expand Down
Loading

0 comments on commit b8f212b

Please sign in to comment.