Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fc3d2c6
实现打开zip文件选择编码。目前问题:不显示文件夹,目录结构被展平
oxygen-dioxide May 18, 2026
d31bcaf
正常显示文件夹
oxygen-dioxide May 29, 2026
722fb3c
可复制、打开文件,仍然无法打开子文件夹的文件
oxygen-dioxide May 29, 2026
50cef74
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide May 31, 2026
acaaa1a
编码选择框移动到右下角
oxygen-dioxide Jun 1, 2026
851da52
formatting
oxygen-dioxide Jun 1, 2026
6ca109c
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 3, 2026
d0cb35f
Improved styling
yair100 Jun 4, 2026
18a2bb2
回退src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs
oxygen-dioxide Jun 5, 2026
b6881b0
ascii文件不显示编码选择框,处理非内置编码
oxygen-dioxide Jun 6, 2026
af1d4d8
Change CurrentEncoding property to non-static
oxygen-dioxide Jun 7, 2026
814c90e
多zip编码管理。bug:切换标签页时变成null
oxygen-dioxide Jun 8, 2026
8f1018a
修复切换页面时zip编码变为null
oxygen-dioxide Jun 22, 2026
3d0b867
formatting
oxygen-dioxide Jun 22, 2026
f527168
修复打开zip文件时编码菜单编码不显示选中项
oxygen-dioxide Jun 23, 2026
df1ec06
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 24, 2026
add308e
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 25, 2026
53f5dc4
GetBasicProperties: 支持不同编码
oxygen-dioxide Jun 25, 2026
d4dfc84
支持加密压缩包
oxygen-dioxide Jun 25, 2026
d234cfa
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 26, 2026
5cf388b
修复复制文件夹仅包含一个文件的bug
oxygen-dioxide Jun 27, 2026
97b252e
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 27, 2026
ae688dd
修复当用户设置使用系统默认编码时,切换到子文件夹再切换回来,重新打开编码菜单时编码被检测结果重新覆盖的bug
oxygen-dioxide Jun 28, 2026
b712c0e
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 28, 2026
0f10498
SelectedItem使用OneWay
oxygen-dioxide Jun 28, 2026
25c806e
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 29, 2026
060486d
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 29, 2026
c186541
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jul 4, 2026
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
4 changes: 2 additions & 2 deletions src/Files.App.Launcher/Files.App.Launcher.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
Expand All @@ -103,7 +103,7 @@
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
Expand Down
14 changes: 14 additions & 0 deletions src/Files.App/Data/Models/CurrentInstanceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ public bool IsPageTypeLibrary
}
}

private string? zipEncodingName;
public string? ZipEncodingName
{
get => zipEncodingName;
set => SetProperty(ref zipEncodingName, value);
}

private bool isZipEncodingUndetermined;
public bool IsZipEncodingUndetermined
{
get => isZipEncodingUndetermined;
set => SetProperty(ref isZipEncodingUndetermined, value);
}

public bool CanCopyPathInPage
{
get => !isPageTypeMtpDevice && !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults && !IsPageTypeReleaseNotes && !IsPageTypeSettings;
Expand Down
11 changes: 11 additions & 0 deletions src/Files.App/UserControls/NavigationToolbar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,17 @@
Orientation="Horizontal"
Spacing="4">

<!-- ZIP Encoding Selector -->
<ComboBox
Comment thread
yair100 marked this conversation as resolved.
Outdated
x:Name="ZipEncodingSelector"
Width="140"
Height="32"
VerticalAlignment="Center"
ItemsSource="{x:Bind ViewModel.ZipEncodingOptions, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedZipEncoding, Mode=TwoWay}"
ToolTipService.ToolTip="{helpers:ResourceString Name=Encoding}"
Visibility="{x:Bind ViewModel.IsZipEncodingSelectorVisible, Mode=OneWay}" />

<!-- Shelf Pane -->
<ToggleButton
x:Name="ShelfPaneToggleButton"
Expand Down
208 changes: 208 additions & 0 deletions src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs
Comment thread
oxygen-dioxide marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using Files.Shared.Helpers;
using ICSharpCode.SharpZipLib.Zip;
using SevenZip;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
Expand Down Expand Up @@ -95,6 +96,9 @@ public static IAsyncOperation<BaseStorageFile> FromPathAsync(string path)

public override IAsyncOperation<IRandomAccessStream> OpenAsync(FileAccessMode accessMode)
{
if (ZipStorageFolder.CurrentEncoding is not null && Path != containerPath)
return OpenWithEncodingAsync(accessMode);

return AsyncInfo.Run((cancellationToken) => SafetyExtensions.Wrap<IRandomAccessStream>(async () =>
{
bool rw = accessMode is FileAccessMode.ReadWrite;
Expand Down Expand Up @@ -136,11 +140,53 @@ public override IAsyncOperation<IRandomAccessStream> OpenAsync(FileAccessMode ac
throw new NotSupportedException("Can't open zip file as RW");
}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
}
private IAsyncOperation<IRandomAccessStream> OpenWithEncodingAsync(FileAccessMode accessMode)
{
return AsyncInfo.Run((cancellationToken) =>
{
return Task.Run<IRandomAccessStream>(() =>
{
bool rw = accessMode is FileAccessMode.ReadWrite;
if (rw)
throw new NotSupportedException("Can't open zip file as RW");

using var zipFile = new ZipFile(containerPath, StringCodec.FromEncoding(ZipStorageFolder.CurrentEncoding!));

if (!string.IsNullOrEmpty(Credentials.Password))
zipFile.Password = Credentials.Password;

var targetName = GetEntryRelativePath();

foreach (ZipEntry entry in zipFile)
{
if (!entry.IsFile)
continue;

if (string.Equals(entry.Name.Replace('\\', '/'), targetName, StringComparison.OrdinalIgnoreCase))
{
var ms = new MemoryStream();
using (var zipStream = zipFile.GetInputStream(entry))
{
zipStream.CopyTo(ms);
}
ms.Position = 0;
return new NonSeekableRandomAccessStreamForRead(ms, (ulong)entry.Size);
}
}

return null;
});
});
}

public override IAsyncOperation<IRandomAccessStream> OpenAsync(FileAccessMode accessMode, StorageOpenOptions options)
=> OpenAsync(accessMode);

public override IAsyncOperation<IRandomAccessStreamWithContentType> OpenReadAsync()
{
if (ZipStorageFolder.CurrentEncoding is not null && Path != containerPath)
return OpenReadWithEncodingAsync();

return AsyncInfo.Run((cancellationToken) => SafetyExtensions.Wrap<IRandomAccessStreamWithContentType>(async () =>
{
if (Path == containerPath)
Expand Down Expand Up @@ -178,8 +224,47 @@ public override IAsyncOperation<IRandomAccessStreamWithContentType> OpenReadAsyn
}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
}

private IAsyncOperation<IRandomAccessStreamWithContentType> OpenReadWithEncodingAsync()
{
return AsyncInfo.Run((cancellationToken) =>
{
return Task.Run<IRandomAccessStreamWithContentType>(() =>
{
using var zipFile = new ZipFile(containerPath, StringCodec.FromEncoding(ZipStorageFolder.CurrentEncoding!));

if (!string.IsNullOrEmpty(Credentials.Password))
zipFile.Password = Credentials.Password;

var targetName = GetEntryRelativePath();

foreach (ZipEntry entry in zipFile)
{
if (!entry.IsFile)
continue;

if (string.Equals(entry.Name.Replace('\\', '/'), targetName, StringComparison.OrdinalIgnoreCase))
{
var ms = new MemoryStream();
using (var zipStream = zipFile.GetInputStream(entry))
{
zipStream.CopyTo(ms);
}
ms.Position = 0;
var nsStream = new NonSeekableRandomAccessStreamForRead(ms, (ulong)entry.Size);
return new StreamWithContentType(nsStream);
}
}

return null;
});
});
}

public override IAsyncOperation<IInputStream> OpenSequentialReadAsync()
{
if (ZipStorageFolder.CurrentEncoding is not null && Path != containerPath)
return OpenSequentialReadWithEncodingAsync();

return AsyncInfo.Run((cancellationToken) => SafetyExtensions.Wrap<IInputStream>(async () =>
{
if (Path == containerPath)
Expand Down Expand Up @@ -215,6 +300,41 @@ public override IAsyncOperation<IInputStream> OpenSequentialReadAsync()
}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
}

private IAsyncOperation<IInputStream> OpenSequentialReadWithEncodingAsync()
{
return AsyncInfo.Run((cancellationToken) =>
{
return Task.Run<IInputStream>(() =>
{
using var zipFile = new ZipFile(containerPath, StringCodec.FromEncoding(ZipStorageFolder.CurrentEncoding!));

if (!string.IsNullOrEmpty(Credentials.Password))
zipFile.Password = Credentials.Password;

var targetName = GetEntryRelativePath();

foreach (ZipEntry entry in zipFile)
{
if (!entry.IsFile)
continue;

if (string.Equals(entry.Name.Replace('\\', '/'), targetName, StringComparison.OrdinalIgnoreCase))
{
var ms = new MemoryStream();
using (var zipStream = zipFile.GetInputStream(entry))
{
zipStream.CopyTo(ms);
}
ms.Position = 0;
return new NonSeekableRandomAccessStreamForRead(ms, (ulong)entry.Size);
}
}

return null;
});
});
}

public override IAsyncOperation<StorageStreamTransaction> OpenTransactedWriteAsync()
=> throw new NotSupportedException();
public override IAsyncOperation<StorageStreamTransaction> OpenTransactedWriteAsync(StorageOpenOptions options)
Expand All @@ -226,6 +346,9 @@ public override IAsyncOperation<BaseStorageFile> CopyAsync(IStorageFolder destin
=> CopyAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists);
public override IAsyncOperation<BaseStorageFile> CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
{
if (ZipStorageFolder.CurrentEncoding is not null && Path != containerPath)
return CopyWithEncodingAsync(destinationFolder, desiredNewName, option);

return AsyncInfo.Run((cancellationToken) => SafetyExtensions.Wrap<BaseStorageFile>(async () =>
{
using SevenZipExtractor zipFile = await OpenZipFileAsync();
Expand Down Expand Up @@ -264,8 +387,57 @@ await SafetyExtensions.WrapAsync(() => zipFile.ExtractFileAsync(entry.Index, out
}
}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
}

private IAsyncOperation<BaseStorageFile> CopyWithEncodingAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
{
return AsyncInfo.Run(async (cancellationToken) =>
{
using var zipFile = new ZipFile(containerPath, StringCodec.FromEncoding(ZipStorageFolder.CurrentEncoding!));

if (!string.IsNullOrEmpty(Credentials.Password))
zipFile.Password = Credentials.Password;

var targetName = GetEntryRelativePath();

foreach (ZipEntry entry in zipFile)
{
if (!entry.IsFile)
continue;

if (string.Equals(entry.Name.Replace('\\', '/'), targetName, StringComparison.OrdinalIgnoreCase))
{
var ms = new MemoryStream();
using (var zipStream = zipFile.GetInputStream(entry))
{
zipStream.CopyTo(ms);
}
ms.Position = 0;

var destFolder = destinationFolder.AsBaseStorageFolder();
if (destFolder is ICreateFileWithStream cwsf)
{
using var inStream = new NonSeekableRandomAccessStreamForRead(ms, (ulong)entry.Size);
return await cwsf.CreateFileAsync(inStream.AsStreamForRead(), desiredNewName, option.Convert());
}
else
{
var destFile = await destFolder.CreateFileAsync(desiredNewName, option.Convert());
await using var outStream = await destFile.OpenStreamForWriteAsync();
ms.Position = 0;
ms.CopyTo(outStream);
return destFile;
}
}
}

return null;
});
}
public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace)
{
if (ZipStorageFolder.CurrentEncoding is not null && Path != containerPath)
return CopyAndReplaceWithEncodingAsync(fileToReplace);

return AsyncInfo.Run((cancellationToken) => SafetyExtensions.WrapAsync(async () =>
{
using SevenZipExtractor zipFile = await OpenZipFileAsync();
Expand All @@ -288,6 +460,36 @@ public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace)
}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
}

private IAsyncAction CopyAndReplaceWithEncodingAsync(IStorageFile fileToReplace)
{
return AsyncInfo.Run(async (cancellationToken) =>
{
using var zipFile = new ZipFile(containerPath, StringCodec.FromEncoding(ZipStorageFolder.CurrentEncoding!));

if (!string.IsNullOrEmpty(Credentials.Password))
zipFile.Password = Credentials.Password;

var targetName = GetEntryRelativePath();

foreach (ZipEntry entry in zipFile)
{
if (!entry.IsFile)
continue;

if (string.Equals(entry.Name.Replace('\\', '/'), targetName, StringComparison.OrdinalIgnoreCase))
{
using var hDestFile = fileToReplace.CreateSafeFileHandle(FileAccess.ReadWrite);
await using (var outStream = new FileStream(hDestFile, FileAccess.Write))
using (var zipStream = zipFile.GetInputStream(entry))
{
zipStream.CopyTo(outStream);
}
return;
}
}
});
}

public override IAsyncAction MoveAsync(IStorageFolder destinationFolder)
=> throw new NotSupportedException();
public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName)
Expand Down Expand Up @@ -399,6 +601,12 @@ public override IAsyncOperation<StorageItemThumbnail> GetThumbnailAsync(Thumbnai
public override IAsyncOperation<StorageItemThumbnail> GetThumbnailAsync(ThumbnailMode mode, uint requestedSize, ThumbnailOptions options)
=> Task.FromResult<StorageItemThumbnail>(null).AsAsyncOperation();

private string GetEntryRelativePath()
{
var relative = Path.Substring(containerPath.Length).Trim('\\', '/');
return relative.Replace('\\', '/');
}

private static bool CheckAccess(string path)
{
try
Expand Down
Loading
Loading