[Peek] Open file in Read only (#25945)

* Only open FilStream in read-only mode; Release propertyStore handle after getting the file properties

(cherry picked from commit 3b1481da2c)

* Update calls to PropertyStoreHelper

* Add disposable property store

* Make GetPropertyStoreFromPath return Disposable property store

* correct typo

* correct typo

* Remove nullable in DisposablePropertyStore

* Add property getters

* Remove usued method

* Correct typo

* Correct typo again...

* Update description
This commit is contained in:
Yawen Hou
2023-05-10 19:00:13 -04:00
committed by GitHub
parent 648f30d1ab
commit 83574c578a
9 changed files with 98 additions and 70 deletions

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Peek.Common.Models;
namespace Peek.Common.Extensions
{
public sealed class DisposablePropertyStore : IDisposable
{
private readonly IPropertyStore _propertyStore;
public DisposablePropertyStore(IPropertyStore propertyStore)
{
_propertyStore = propertyStore;
}
public void GetValue(ref PropertyKey key, out PropVariant pv)
{
_propertyStore!.GetValue(ref key, out pv);
}
public void Dispose()
{
Marshal.ReleaseComObject(_propertyStore);
}
}
}

View File

@@ -21,9 +21,8 @@ namespace Peek.Common.Extensions
{ {
Size? size = null; Size? size = null;
var propertyStore = item.PropertyStore; var width = item.Width;
var width = propertyStore.TryGetUInt(PropertyKey.ImageHorizontalSize); var height = item.Height;
var height = propertyStore.TryGetUInt(PropertyKey.ImageVerticalSize);
if (width != null && height != null) if (width != null && height != null)
{ {
@@ -36,8 +35,7 @@ namespace Peek.Common.Extensions
public static Size? GetSvgSize(this IFileSystemItem item) public static Size? GetSvgSize(this IFileSystemItem item)
{ {
Size? size = null; Size? size = null;
using (FileStream stream = new FileStream(item.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
using (FileStream stream = System.IO.File.OpenRead(item.Path))
{ {
XmlReaderSettings settings = new XmlReaderSettings(); XmlReaderSettings settings = new XmlReaderSettings();
settings.Async = true; settings.Async = true;
@@ -95,8 +93,7 @@ namespace Peek.Common.Extensions
sizeInBytes = (ulong)folder.Size; sizeInBytes = (ulong)folder.Size;
break; break;
case FileItem _: case FileItem _:
var propertyStore = item.PropertyStore; sizeInBytes = item.FileSizeBytes;
sizeInBytes = propertyStore.TryGetULong(PropertyKey.FileSizeBytes) ?? 0;
break; break;
} }
@@ -117,8 +114,7 @@ namespace Peek.Common.Extensions
contentType = storageFolder.DisplayType; contentType = storageFolder.DisplayType;
break; break;
default: default:
var propertyStore = item.PropertyStore; contentType = item.FileType;
contentType = propertyStore.TryGetString(PropertyKey.FileType) ?? string.Empty;
break; break;
} }

View File

@@ -18,7 +18,7 @@ namespace Peek.Common.Extensions
/// <param name="propertyStore">The property store</param> /// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param> /// <param name="key">The pkey</param>
/// <returns>The uint value</returns> /// <returns>The uint value</returns>
public static uint? TryGetUInt(this IPropertyStore propertyStore, PropertyKey key) public static uint? TryGetUInt(this DisposablePropertyStore propertyStore, PropertyKey key)
{ {
if (propertyStore == null) if (propertyStore == null)
{ {
@@ -54,7 +54,7 @@ namespace Peek.Common.Extensions
/// <param name="propertyStore">The property store</param> /// <param name="propertyStore">The property store</param>
/// <param name="key">the pkey</param> /// <param name="key">the pkey</param>
/// <returns>the ulong value</returns> /// <returns>the ulong value</returns>
public static ulong? TryGetULong(this IPropertyStore propertyStore, PropertyKey key) public static ulong? TryGetULong(this DisposablePropertyStore propertyStore, PropertyKey key)
{ {
if (propertyStore == null) if (propertyStore == null)
{ {
@@ -89,7 +89,7 @@ namespace Peek.Common.Extensions
/// <param name="propertyStore">The property store</param> /// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param> /// <param name="key">The pkey</param>
/// <returns>The string value</returns> /// <returns>The string value</returns>
public static string? TryGetString(this IPropertyStore propertyStore, PropertyKey key) public static string? TryGetString(this DisposablePropertyStore propertyStore, PropertyKey key)
{ {
if (propertyStore == null) if (propertyStore == null)
{ {
@@ -116,46 +116,5 @@ namespace Peek.Common.Extensions
return null; return null;
} }
} }
/// <summary>
/// Helper method that retrieves an array of string values from the given property store.
/// </summary>
/// <param name="propertyStore">The property store</param>
/// <param name="key">The pkey</param>
/// <returns>The array of string values</returns>
public static string[]? TryGetStringArray(this IPropertyStore propertyStore, PropertyKey key)
{
if (propertyStore == null)
{
return null;
}
try
{
PropVariant propVar;
propertyStore.GetValue(ref key, out propVar);
List<string>? values = null;
if ((VarEnum)propVar.Vt == (VarEnum.VT_LPWSTR | VarEnum.VT_VECTOR))
{
values = new List<string>();
for (int elementIndex = 0; elementIndex < propVar.Calpwstr.CElems; elementIndex++)
{
var stringVal = Marshal.PtrToStringUni(Marshal.ReadIntPtr(propVar.Calpwstr.PElems, elementIndex));
if (stringVal != null)
{
values.Add(stringVal);
}
}
}
return values?.ToArray();
}
catch (Exception)
{
return null;
}
}
} }
} }

View File

@@ -3,9 +3,9 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Peek.Common.Extensions;
using Peek.Common.Models; using Peek.Common.Models;
using Windows.Win32.UI.Shell.PropertiesSystem; using Windows.Win32.UI.Shell.PropertiesSystem;
@@ -14,12 +14,48 @@ namespace Peek.Common.Helpers
public static partial class PropertyStoreHelper public static partial class PropertyStoreHelper
{ {
/// <summary> /// <summary>
/// Gets a IPropertyStore interface from the given path. /// Gets a uint type value from PropertyStore from the given item.
/// </summary>
/// <param name="path">The file/folder path</param>
/// <param name="key">The property key</param>
/// <returns>a nullable uint</returns>
public static uint? TryGetUintProperty(string path, PropertyKey key)
{
using DisposablePropertyStore propertyStore = GetPropertyStoreFromPath(path);
return propertyStore.TryGetUInt(key);
}
/// <summary>
/// Gets a ulong type value from PropertyStore from the given item.
/// </summary>
/// <param name="path">The file/folder path</param>
/// <param name="key">The property key</param>
/// <returns>a nullable ulong</returns>
public static ulong? TryGetUlongProperty(string path, PropertyKey key)
{
using DisposablePropertyStore propertyStore = GetPropertyStoreFromPath(path);
return propertyStore.TryGetULong(key);
}
/// <summary>
/// Gets a string type value from PropertyStore from the given item.
/// </summary>
/// <param name="path">The file/folder path</param>
/// <param name="key">The property key</param>
/// <returns>a nullable string</returns>
public static string? TryGetStringProperty(string path, PropertyKey key)
{
using DisposablePropertyStore propertyStore = GetPropertyStoreFromPath(path);
return propertyStore.TryGetString(key);
}
/// <summary>
/// Gets a IPropertyStore interface (wrapped in DisposablePropertyStore) from the given path.
/// </summary> /// </summary>
/// <param name="path">The file/folder path</param> /// <param name="path">The file/folder path</param>
/// <param name="flags">The property store flags</param> /// <param name="flags">The property store flags</param>
/// <returns>an IPropertyStroe interface</returns> /// <returns>an IPropertyStroe interface</returns>
public static IPropertyStore GetPropertyStoreFromPath(string path, GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_EXTRINSICPROPERTIES) private static DisposablePropertyStore GetPropertyStoreFromPath(string path, GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_EXTRINSICPROPERTIES)
{ {
IShellItem2? shellItem2 = null; IShellItem2? shellItem2 = null;
IntPtr ppPropertyStore = IntPtr.Zero; IntPtr ppPropertyStore = IntPtr.Zero;
@@ -40,7 +76,7 @@ namespace Peek.Common.Helpers
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "GetPropertyStore returned hresult={0}", hr)); throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "GetPropertyStore returned hresult={0}", hr));
} }
return (IPropertyStore)Marshal.GetObjectForIUnknown(ppPropertyStore); return new DisposablePropertyStore((IPropertyStore)Marshal.GetObjectForIUnknown(ppPropertyStore));
} }
finally finally
{ {

View File

@@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Peek.Common.Extensions;
using Peek.Common.Helpers; using Peek.Common.Helpers;
using Windows.Storage; using Windows.Storage;
@@ -15,18 +17,13 @@ namespace Peek.Common.Models
{ {
private StorageFile? storageFile; private StorageFile? storageFile;
private Lazy<IPropertyStore> _propertyStore;
public FileItem(string path) public FileItem(string path)
{ {
Path = path; Path = path;
_propertyStore = new(() => PropertyStoreHelper.GetPropertyStoreFromPath(Path));
} }
public string Path { get; init; } public string Path { get; init; }
public IPropertyStore PropertyStore => _propertyStore.Value;
public async Task<IStorageItem?> GetStorageItemAsync() public async Task<IStorageItem?> GetStorageItemAsync()
{ {
return await GetStorageFileAsync(); return await GetStorageFileAsync();

View File

@@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Peek.Common.Extensions;
using Peek.Common.Helpers; using Peek.Common.Helpers;
using Windows.Storage; using Windows.Storage;
@@ -15,18 +17,13 @@ namespace Peek.Common.Models
{ {
private StorageFolder? storageFolder; private StorageFolder? storageFolder;
private Lazy<IPropertyStore> _propertyStore;
public FolderItem(string path) public FolderItem(string path)
{ {
Path = path; Path = path;
_propertyStore = new(() => PropertyStoreHelper.GetPropertyStoreFromPath(Path));
} }
public string Path { get; init; } public string Path { get; init; }
public IPropertyStore PropertyStore => _propertyStore.Value;
public async Task<IStorageItem?> GetStorageItemAsync() public async Task<IStorageItem?> GetStorageItemAsync()
{ {
return await GetStorageFolderAsync(); return await GetStorageFolderAsync();

View File

@@ -5,6 +5,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Peek.Common.Extensions;
using Peek.Common.Helpers; using Peek.Common.Helpers;
using Windows.Storage; using Windows.Storage;
@@ -22,7 +23,13 @@ namespace Peek.Common.Models
public string Path { get; init; } public string Path { get; init; }
public IPropertyStore PropertyStore { get; } public uint? Width => PropertyStoreHelper.TryGetUintProperty(Path, PropertyKey.ImageHorizontalSize);
public uint? Height => PropertyStoreHelper.TryGetUintProperty(Path, PropertyKey.ImageVerticalSize);
public ulong FileSizeBytes => PropertyStoreHelper.TryGetUlongProperty(Path, PropertyKey.FileSizeBytes) ?? 0;
public string FileType => PropertyStoreHelper.TryGetStringProperty(Path, PropertyKey.FileType) ?? string.Empty;
public Task<IStorageItem?> GetStorageItemAsync(); public Task<IStorageItem?> GetStorageItemAsync();
} }

View File

@@ -210,7 +210,7 @@ namespace Peek.FilePreviewer.Previewers
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
using FileStream stream = File.OpenRead(Item.Path); using FileStream stream = ReadHelper.OpenReadOnly(Item.Path);
await Dispatcher.RunOnUiThread(async () => await Dispatcher.RunOnUiThread(async () =>
{ {

View File

@@ -12,11 +12,16 @@ namespace Peek.FilePreviewer.Previewers
{ {
public static async Task<string> Read(string path) public static async Task<string> Read(string path)
{ {
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); using var fs = OpenReadOnly(path);
using var sr = new StreamReader(fs, Encoding.UTF8); using var sr = new StreamReader(fs, Encoding.UTF8);
string content = await sr.ReadToEndAsync(); string content = await sr.ReadToEndAsync();
return content; return content;
} }
public static FileStream OpenReadOnly(string path)
{
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
}
} }
} }