Skip to content

Commit b8f212b

Browse files
committed
Prepared Reference Files for overhaul
1 parent 1eecf56 commit b8f212b

File tree

8 files changed

+214
-44
lines changed

8 files changed

+214
-44
lines changed

ClipboardCanvas/CanavsPasteModels/BasePasteModel.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ protected async Task<SafeWrapperResult> SaveDataToFileInternal()
149149
if (IsContentAsReference && await sourceItem == null)
150150
{
151151
// We need to update the reference file because the one we created is empty
152-
ReferenceFile referenceFile = await ReferenceFile.GetReferenceFile(associatedFile);
153-
return await referenceFile.UpdateReference(_pastedItem.Path);
152+
ReferenceFile referenceFile = await ReferenceFile.CreateReferenceFileFromFile(associatedFile, _pastedItem);
153+
return referenceFile.LastError;
154154
}
155155
else
156156
{

ClipboardCanvas/Extensions/LinqExtensions.cs

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ namespace ClipboardCanvas.Extensions
55
{
66
public static class LinqExtensions
77
{
8+
/// <summary>
9+
/// Determines whether <paramref name="enumerable"/> is empty or not.
10+
/// <br/><br/>
11+
/// Remarks:
12+
/// <br/>
13+
/// This function is faster than enumerable.Count == 0 since it'll only iterate one element instead of all elements.
14+
/// <br/>
15+
/// This function is null-safe.
16+
/// </summary>
17+
/// <typeparam name="T"></typeparam>
18+
/// <param name="enumerable"></param>
19+
/// <returns></returns>
820
public static bool IsEmpty<T>(this IEnumerable<T> enumerable) => enumerable == null || !enumerable.Any();
921

1022
public static bool CheckEveryNotNull<T>(this IEnumerable<T> enumerable) => !(enumerable == null || enumerable.Any((item) => item.IsNull()));

ClipboardCanvas/Extensions/SafetyExtensions.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using Microsoft.Win32.SafeHandles;
2+
using System;
23
using System.Diagnostics;
34

45
namespace ClipboardCanvas.Extensions
@@ -24,5 +25,8 @@ public static T PreventNull<T>(this T element, T defaultValue, bool throwOnNullD
2425
return element;
2526
}
2627
}
28+
29+
public static SafeFileHandle ToSafeFileHandle(this IntPtr unsafeHandle) =>
30+
new SafeFileHandle(unsafeHandle, true);
2731
}
2832
}

ClipboardCanvas/ReferenceItems/ReferenceFile.cs

+95-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Newtonsoft.Json;
1+
using System;
2+
using Newtonsoft.Json;
23
using System.IO;
34
using System.Threading.Tasks;
45
using Windows.Storage;
@@ -7,7 +8,7 @@
78
using ClipboardCanvas.Exceptions;
89
using ClipboardCanvas.Helpers.Filesystem;
910
using ClipboardCanvas.Helpers.SafetyHelpers;
10-
using System;
11+
using ClipboardCanvas.UnsafeNative;
1112

1213
namespace ClipboardCanvas.ReferenceItems
1314
{
@@ -30,12 +31,57 @@ private ReferenceFile(StorageFile innerFile, IStorageItem referencedItem)
3031
this.ReferencedItem = referencedItem;
3132
}
3233

33-
public async Task<SafeWrapperResult> UpdateReference(string newPath)
34+
/// <summary>
35+
/// Updates the underlying file and fileData
36+
/// </summary>
37+
/// <param name="newPath"></param>
38+
/// <returns></returns>
39+
private async Task<SafeWrapperResult> UpdateReference(string newPath, bool regenerateReferenceItem)
3440
{
35-
_referenceFileData = new ReferenceFileData(newPath);
41+
if (!StorageHelpers.Existsh(newPath))
42+
{
43+
return new SafeWrapperResult(OperationErrorCode.NotFound, new FileNotFoundException(), "File does not exist.");
44+
}
45+
46+
long fileId = UnsafeNativeHelpers.GetFileId(newPath);
47+
bool isRepairable = fileId != -1;
48+
49+
_referenceFileData = new ReferenceFileData(newPath, fileId, isRepairable);
3650
string serialized = JsonConvert.SerializeObject(_referenceFileData, Formatting.Indented);
3751

38-
return await FilesystemOperations.WriteFileText(_innerReferenceFile, serialized);
52+
SafeWrapperResult writeFileTextResult = await FilesystemOperations.WriteFileText(_innerReferenceFile, serialized);
53+
if (!writeFileTextResult)
54+
{
55+
return writeFileTextResult;
56+
}
57+
58+
if (regenerateReferenceItem)
59+
{
60+
SafeWrapper<IStorageItem> referencedItem = await StorageHelpers.ToStorageItemWithError<IStorageItem>(newPath);
61+
this.ReferencedItem = referencedItem.Result;
62+
63+
return referencedItem;
64+
}
65+
66+
return SafeWrapperResult.SUCCESS;
67+
}
68+
69+
public static async Task<ReferenceFile> CreateReferenceFileFromFile(StorageFile emptyReferenceFile, IStorageItem referencedItem)
70+
{
71+
if (!FileHelpers.IsPathEqualExtension(emptyReferenceFile.Path, Constants.FileSystem.REFERENCE_FILE_EXTENSION))
72+
{
73+
return new ReferenceFile(emptyReferenceFile, null)
74+
{
75+
LastError = new SafeWrapperResult(OperationErrorCode.InvalidArgument, new ArgumentException(), "Empty file uses invalid Reference File extension.")
76+
};
77+
}
78+
79+
ReferenceFile referenceFile = new ReferenceFile(emptyReferenceFile, referencedItem);
80+
81+
SafeWrapperResult result = await referenceFile.UpdateReference(referencedItem.Path, false);
82+
referenceFile.LastError = result;
83+
84+
return referenceFile;
3985
}
4086

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

53-
public static async Task<ReferenceFile> GetReferenceFile(StorageFile referenceFile)
99+
public static async Task<ReferenceFile> GetReferenceFile(StorageFile file)
54100
{
55101
// The file is not a Reference File
56-
if (!IsReferenceFile(referenceFile))
102+
if (!IsReferenceFile(file))
57103
{
58104
return null;
59105
}
60106

61-
// The file does not exist
62-
if (!StorageHelpers.Existsh(referenceFile.Path))
107+
// The referenceFile does not exist
108+
if (!StorageHelpers.Existsh(file.Path))
63109
{
64-
return new ReferenceFile(referenceFile, null)
110+
return new ReferenceFile(file, null)
65111
{
66112
LastError = new SafeWrapperResult(OperationErrorCode.NotFound, new FileNotFoundException(), "Couldn't resolve item associated with path.")
67113
};
68114
}
69115

70-
SafeWrapper<ReferenceFileData> referenceFileData = await ReadData(referenceFile);
116+
SafeWrapper<ReferenceFileData> referenceFileData = await ReadData(file);
117+
118+
ReferenceFile referenceFile = await GetFile(file, referenceFileData);
119+
if (referenceFile.LastError)
120+
{
121+
if (!referenceFileData.Result.isRepairable) // Bad FileId but the path is correct
122+
{
123+
// Repair the FileId
124+
await referenceFile.UpdateReference(referenceFileData.Result.path, false);
125+
}
126+
}
127+
else
128+
{
129+
if (referenceFile.LastError == OperationErrorCode.InvalidArgument || referenceFile.LastError == OperationErrorCode.NotFound)
130+
{
131+
if (referenceFileData.Result?.isRepairable ?? false)
132+
{
133+
// Repair it
134+
SafeWrapperResult result = await RepairReferenceFile(referenceFile, referenceFileData);
135+
referenceFile.LastError = result;
136+
}
137+
}
138+
}
71139

72-
return await GetFile(referenceFile, referenceFileData);
140+
return referenceFile;
73141
}
74142

75143
private static async Task<ReferenceFile> GetFile(StorageFile referenceFile, SafeWrapper<ReferenceFileData> referenceFileData)
@@ -116,6 +184,21 @@ private static async Task<ReferenceFile> GetFile(StorageFile referenceFile, Safe
116184
};
117185
}
118186

187+
private static async Task<SafeWrapperResult> RepairReferenceFile(ReferenceFile referenceFile, ReferenceFileData referenceFileData)
188+
{
189+
if (referenceFileData.fileId == -1)
190+
{
191+
return referenceFile.LastError;
192+
}
193+
194+
// Get path from FileId...
195+
196+
// TODO: Implement that when it becomes possible
197+
198+
// for now, return the error
199+
return referenceFile.LastError;
200+
}
201+
119202
public static bool IsReferenceFile(StorageFile file)
120203
{
121204
return FileHelpers.IsPathEqualExtension(file?.Path, Constants.FileSystem.REFERENCE_FILE_EXTENSION);
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
using System;
2-
using System.Collections.Generic;
3-
using Newtonsoft.Json;
42

53
namespace ClipboardCanvas.ReferenceItems
64
{
75
[Serializable]
86
public sealed class ReferenceFileData
97
{
10-
[JsonRequired]
118
public readonly string path;
129

13-
[JsonConstructor]
14-
public ReferenceFileData(string path)
15-
{
16-
this.path = path;
17-
}
10+
public readonly long fileId;
1811

19-
public Dictionary<string, object> ToRawData()
20-
{
21-
Dictionary<string, object> rawData = new Dictionary<string, object>();
12+
public readonly bool isRepairable;
2213

23-
rawData.Add(nameof(path), path);
24-
25-
return rawData;
14+
public ReferenceFileData(string path, long fileId, bool isRepairable)
15+
{
16+
this.path = path;
17+
this.fileId = fileId;
18+
this.isRepairable = isRepairable;
2619
}
2720
}
2821
}

ClipboardCanvas/UnsafeNative/UnsafeNativeApis.cs

+8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public static extern bool GetFileAttributesExFromApp(
4949
GET_FILEEX_INFO_LEVELS fInfoLevelId,
5050
out WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
5151

52+
[DllImport("api-ms-win-core-file-l2-1-1.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
53+
[return: MarshalAs(UnmanagedType.Bool)]
54+
public static extern bool GetFileInformationByHandleEx(
55+
IntPtr hFile,
56+
FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
57+
out BY_HANDLE_FILE_INFORMATION lpFileInformation,
58+
uint dwBufferSize);
59+
5260
[DllImport("api-ms-win-core-handle-l1-1-0.dll")]
5361
public static extern bool CloseHandle(IntPtr hObject);
5462
}

ClipboardCanvas/UnsafeNative/UnsafeNativeDataModels.cs

+41
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ public struct WIN32_FILE_ATTRIBUTE_DATA
1717
public uint nFileSizeLow;
1818
}
1919

20+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
21+
public struct BY_HANDLE_FILE_INFORMATION
22+
{
23+
public uint FileAttributes;
24+
public FILETIME CreationTime;
25+
public FILETIME LastAccessTime;
26+
public FILETIME LastWriteTime;
27+
public uint VolumeSerialNumber;
28+
public uint FileSizeHigh;
29+
public uint FileSizeLow;
30+
public uint NumberOfLinks;
31+
public uint FileIndexHigh;
32+
public uint FileIndexLow;
33+
}
34+
2035
public enum GET_FILEEX_INFO_LEVELS
2136
{
2237
GetFileExInfoStandard,
@@ -51,6 +66,32 @@ public enum File_Attributes : uint
5166
FirstPipeInstance = 0x00080000
5267
}
5368

69+
public enum FILE_INFO_BY_HANDLE_CLASS
70+
{
71+
FileBasicInfo = 0,
72+
FileStandardInfo = 1,
73+
FileNameInfo = 2,
74+
FileRenameInfo = 3,
75+
FileDispositionInfo = 4,
76+
FileAllocationInfo = 5,
77+
FileEndOfFileInfo = 6,
78+
FileStreamInfo = 7,
79+
FileCompressionInfo = 8,
80+
FileAttributeTagInfo = 9,
81+
FileIdBothDirectoryInfo = 10,
82+
FileIdBothDirectoryRestartInfo = 11,
83+
FileIoPriorityHintInfo = 12,
84+
FileRemoteProtocolInfo = 13,
85+
FileFullDirectoryInfo = 14,
86+
FileFullDirectoryRestartInfo = 15,
87+
FileStorageInfo = 16,
88+
FileAlignmentInfo = 17,
89+
FileIdInfo = 18,
90+
FileIdExtdDirectoryInfo = 19,
91+
FileIdExtdDirectoryRestartInfo = 20,
92+
MaximumFileInfoByHandlesClass = 21
93+
}
94+
5495
public const uint GENERIC_READ = 0x80000000;
5596
public const uint GENERIC_WRITE = 0x40000000;
5697

0 commit comments

Comments
 (0)