// Copyright (C) 2026, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
namespace Duplicati.Library.Common.IO;
///
/// Helper class for working with file attributes not listed in
///
public static class ExtendedFileAttributes
{
///
/// Comprehensive mapping of Windows FILE_ATTRIBUTE_* constants from winnt.h
///
[SupportedOSPlatform("windows")]
[Flags]
public enum Win32FileAttributes : uint
{
/// No attributes set.
None = 0x00000000,
/// FILE_ATTRIBUTE_READONLY: File is read-only.
ReadOnly = 0x00000001,
/// FILE_ATTRIBUTE_HIDDEN: File or directory is hidden.
Hidden = 0x00000002,
/// FILE_ATTRIBUTE_SYSTEM: Part of or used exclusively by the OS.
System = 0x00000004,
/// FILE_ATTRIBUTE_DIRECTORY: Identifies a directory handle.
Directory = 0x00000010,
/// FILE_ATTRIBUTE_ARCHIVE: Marked for backup or removal.
Archive = 0x00000020,
/// FILE_ATTRIBUTE_DEVICE: Reserved for system use.
Device = 0x00000040,
/// FILE_ATTRIBUTE_NORMAL: No other attributes set (only valid when used alone).
Normal = 0x00000080,
/// FILE_ATTRIBUTE_TEMPORARY: Used for temporary storage; cached heavily.
Temporary = 0x00000100,
/// FILE_ATTRIBUTE_SPARSE_FILE: File is a sparse file.
SparseFile = 0x00000200,
/// FILE_ATTRIBUTE_REPARSE_POINT: Has an associated reparse point or symlink.
ReparsePoint = 0x00000400,
/// FILE_ATTRIBUTE_COMPRESSED: All data in the file/directory is compressed.
Compressed = 0x00000800,
/// FILE_ATTRIBUTE_OFFLINE: Data physically moved to offline storage.
Offline = 0x00001000,
/// FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: Will not be indexed by content indexing services.
NotContentIndexed = 0x00002000,
/// FILE_ATTRIBUTE_ENCRYPTED: File or directory is encrypted.
Encrypted = 0x00004000,
/// FILE_ATTRIBUTE_INTEGRITY_STREAM: ReFS directory/stream configured with integrity.
IntegrityStream = 0x00008000,
/// FILE_ATTRIBUTE_VIRTUAL: Reserved for internal system use.
Virtual = 0x00010000,
/// FILE_ATTRIBUTE_NO_SCRUB_DATA: Stream skipped by background data integrity scanner.
NoScrubData = 0x00020000,
/// FILE_ATTRIBUTE_EA: File/directory has extended attributes (Internal use only).
ExtendedAttributes = 0x00040000,
/// FILE_ATTRIBUTE_RECALL_ON_OPEN: Completely virtual, has no local physical representation.
RecallOnOpen = 0x00040000,
/// FILE_ATTRIBUTE_PINNED: Cloud file system user intent to keep fully present locally.
Pinned = 0x00080000,
/// FILE_ATTRIBUTE_UNPINNED: Cloud file system intent to NOT keep present unless active.
Unpinned = 0x00100000,
/// FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS: Not fully present locally; reading will fetch from cloud.
RecallOnDataAccess = 0x00400000
}
///
/// Merge the default C# FileAttributes with the Win32FileAttributes and return the union.
///
private static Lazy> _nameToValueMap = new Lazy>(() =>
{
var map = new Dictionary(StringComparer.OrdinalIgnoreCase);
var included = new HashSet();
// Add the built-in values
foreach (var name in Enum.GetNames())
{
map[name] = (uint)Enum.Parse(name);
included.Add(map[name]);
}
if (OperatingSystem.IsWindows())
{
// Merge in the Win32FileAttributes, but prefer the built-in values
foreach (var name in Enum.GetNames())
{
if (map.ContainsKey(name))
continue;
var value = (uint)Enum.Parse(name);
if (included.Contains(value))
continue;
map[name] = value;
}
}
return map;
});
///
/// Get the names of all known file attributes, including both the built-in and Win32 values.
///
/// The list of known file attribute names.
public static IEnumerable GetNames(bool includeNone)
=> _nameToValueMap.Value.Keys.Where(x => includeNone || x != nameof(FileAttributes.None));
///
/// Get the file attributes that mark files as not local on the system.
///
public static FileAttributes NonLocalAttributes
=> OperatingSystem.IsWindows()
? (FileAttributes)(Win32FileAttributes.Offline | Win32FileAttributes.RecallOnOpen | Win32FileAttributes.RecallOnDataAccess)
: FileAttributes.Offline;
///
/// Parse a comma-separated list of file attribute names into a FileAttributes value.
///
/// The comma-separated list of file attribute names.
/// The parsed FileAttributes value.
public static FileAttributes Parse(string names)
{
if (string.IsNullOrWhiteSpace(names))
return FileAttributes.None;
var result = 0u;
foreach (var name in names.Split([','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
if (_nameToValueMap.Value.TryGetValue(name, out var value))
result |= value;
}
return (FileAttributes)result;
}
}