Change IsolatedStorageFile path for mobile (#83380)
authorMeri Khamoyan <96171496+mkhamoyan@users.noreply.github.com>
Thu, 11 May 2023 09:33:40 +0000 (13:33 +0400)
committerGitHub <noreply@github.com>
Thu, 11 May 2023 09:33:40 +0000 (13:33 +0400)
 Updated isolated storage root path for mobile platforms
---------
Co-authored-by: Alexander Köplinger <alex.koeplinger@outlook.com>
18 files changed:
src/libraries/System.IO.IsolatedStorage/src/System.IO.IsolatedStorage.csproj
src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.AnyMobile.cs
src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.NonMobile.cs
src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.Win32Unix.cs [deleted file]
src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.cs
src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.AnyMobile.cs
src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.NonMobile.cs
src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/IsolatedStorageFile.cs
src/libraries/System.IO.IsolatedStorage/tests/System.IO.IsolatedStorage.Tests.csproj
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/ContainsUnknownFilesTests.cs
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/FileExistsTests.cs
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/GetStoreTests.cs
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.AnyMobile.cs [new file with mode: 0644]
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.NonMobile.cs [new file with mode: 0644]
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.Win32Unix.cs
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.AnyMobile.cs
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.NonMobile.cs
src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/TestHelper.cs

index d56c44c..f8e19c7 100644 (file)
@@ -14,7 +14,6 @@
     <Compile Include="System\IO\IsolatedStorage\IsolatedStorage.cs" />
     <Compile Include="System\IO\IsolatedStorage\IsolatedStorageScope.cs" />
     <Compile Include="System\IO\IsolatedStorage\Helper.cs" />
-    <Compile Include="System\IO\IsolatedStorage\Helper.Win32Unix.cs" />
     <Compile Include="System\IO\IsolatedStorage\INormalizeForIsolatedStorage.cs" />
     <Compile Include="$(CommonPath)System\Security\IdentityHelper.cs"
              Link="Common\System\Security\IdentityHelper.cs" />
index 4121814..2600c47 100644 (file)
@@ -5,6 +5,68 @@ namespace System.IO.IsolatedStorage
 {
     internal static partial class Helper
     {
+        // we're using a different directory name for compatibility with legacy Xamarin
         public const string IsolatedStorageDirectoryName = ".isolated-storage";
+
+        internal static string GetDataDirectory(IsolatedStorageScope scope)
+        {
+            // In legacy Xamarin for Roaming Scope we were using Environment.SpecialFolder.LocalApplicationData
+            // In .Net 7 for Roaming Scope we are using Environment.SpecialFolder.ApplicationData
+            // e.g. Android .Net 7 path = /data/user/0/{packageName}/files/.isolated-storage/{hash}/{hash}/AppFiles/
+            // e.g. Android Xamarin path = /data/user/0/{packageName}/files/.config/.isolated-storage/
+            // e.g. iOS .Net 7 path = /Users/userName/{packageName}/Documents/.isolated-storage/{hash}/{hash}/AppFiles/
+            // e.g. iOS Xamarin path = /Users/userName/{packageName}/Documents/.config/.isolated-storage/
+            //
+            // Since we shipped that behavior as part of .NET 7 we can't change this now or upgraded apps wouldn't find their files anymore.
+            // We need to look for an existing directory first before using the legacy Xamarin approach.
+
+            Environment.SpecialFolder specialFolder =
+            IsMachine(scope) ? Environment.SpecialFolder.CommonApplicationData : // e.g. /usr/share;
+            IsRoaming(scope) ? Environment.SpecialFolder.ApplicationData : // e.g. /data/user/0/{packageName}/files/.config;
+            Environment.SpecialFolder.LocalApplicationData; // e.g. /data/user/0/{packageName}/files;
+
+            string dataDirectory = Environment.GetFolderPath(specialFolder);
+            dataDirectory = Path.Combine(dataDirectory, IsolatedStorageDirectoryName);
+            if (Directory.Exists(dataDirectory))
+            {
+                return dataDirectory;
+            }
+            // Otherwise return legacy xamarin path
+            else
+            {
+                // In .Net 7 for Android SpecialFolder.LocalApplicationData returns "/data/user/0/{packageName}/files"
+                // while in Xamarin it was "/data/user/0/{packageName}/files/.local/share"
+                // For Android we need to hardcode Xamarin path for compatibility with legacy Xamarin
+                if (OperatingSystem.IsAndroid() && IsRoaming(scope))
+                {
+                    dataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + ".local/share";
+                    Directory.CreateDirectory(dataDirectory);
+                }
+                else
+                {
+                    specialFolder =
+                    IsMachine(scope) ? Environment.SpecialFolder.CommonApplicationData :
+                    IsRoaming(scope) ? Environment.SpecialFolder.LocalApplicationData :
+                    Environment.SpecialFolder.ApplicationData;
+
+                    dataDirectory = Environment.GetFolderPath(specialFolder, Environment.SpecialFolderOption.Create);
+                }
+
+                dataDirectory = Path.Combine(dataDirectory, IsolatedStorageDirectoryName);
+            }
+
+            return dataDirectory;
+        }
+
+        internal static string GetRandomDirectory(string rootDirectory, IsolatedStorageScope _)
+        {
+            // In legacy Xamarin we didn't have a random directory inside of the isolated storage root for each app,
+            // we tried to preserve that in https://github.com/dotnet/runtime/pull/75541 but the fix wasn't complete enough
+            // and we still created random directories when not using the Roaming scope.
+            //
+            // Since we shipped that behavior as part of .NET 7 we can't change this now or upgraded apps wouldn't find their files anymore.
+            // We need to look for an existing random directory first before using the plain root directory.
+            return GetExistingRandomDirectory(rootDirectory) ?? rootDirectory;
+        }
     }
 }
index cde27b6..1112817 100644 (file)
@@ -1,10 +1,68 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Threading;
+using System.Security;
+
 namespace System.IO.IsolatedStorage
 {
     internal static partial class Helper
     {
         public const string IsolatedStorageDirectoryName = "IsolatedStorage";
+
+        internal static string GetDataDirectory(IsolatedStorageScope scope)
+        {
+            // This is the relevant special folder for the given scope plus IsolatedStorageDirectoryName.
+            // It is meant to replicate the behavior of the VM ComIsolatedStorage::GetRootDir().
+
+            // (note that Silverlight used "CoreIsolatedStorage" for a directory name and did not support machine scope)
+
+            Environment.SpecialFolder specialFolder =
+            IsMachine(scope) ? Environment.SpecialFolder.CommonApplicationData : // e.g. C:\ProgramData
+            IsRoaming(scope) ? Environment.SpecialFolder.ApplicationData : // e.g. C:\Users\Joe\AppData\Roaming
+            Environment.SpecialFolder.LocalApplicationData; // e.g. C:\Users\Joe\AppData\Local
+
+            string dataDirectory = Environment.GetFolderPath(specialFolder, Environment.SpecialFolderOption.Create);
+            dataDirectory = Path.Combine(dataDirectory, IsolatedStorageDirectoryName);
+
+            return dataDirectory;
+        }
+
+        internal static string GetRandomDirectory(string rootDirectory, IsolatedStorageScope scope)
+        {
+            string? randomDirectory = GetExistingRandomDirectory(rootDirectory);
+            if (string.IsNullOrEmpty(randomDirectory))
+            {
+                using (Mutex m = CreateMutexNotOwned(rootDirectory))
+                {
+                    if (!m.WaitOne())
+                    {
+                        throw new IsolatedStorageException(SR.IsolatedStorage_Init);
+                    }
+
+                    try
+                    {
+                        randomDirectory = GetExistingRandomDirectory(rootDirectory);
+                        if (string.IsNullOrEmpty(randomDirectory))
+                        {
+                            // Someone else hasn't created the directory before we took the lock
+                            randomDirectory = Path.Combine(rootDirectory, Path.GetRandomFileName(), Path.GetRandomFileName());
+                            CreateDirectory(randomDirectory, scope);
+                        }
+                    }
+                    finally
+                    {
+                        m.ReleaseMutex();
+                    }
+                }
+            }
+
+            return randomDirectory;
+        }
+
+        private static Mutex CreateMutexNotOwned(string pathName)
+        {
+            return new Mutex(initiallyOwned: false, name: @"Global\" + IdentityHelper.GetStrongHashSuitableForObjectName(pathName));
+        }
     }
 }
diff --git a/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.Win32Unix.cs b/src/libraries/System.IO.IsolatedStorage/src/System/IO/IsolatedStorage/Helper.Win32Unix.cs
deleted file mode 100644 (file)
index 876b9e1..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-using System.Security;
-using System.Threading;
-
-namespace System.IO.IsolatedStorage
-{
-    internal static partial class Helper
-    {
-        internal static string GetDataDirectory(IsolatedStorageScope scope)
-        {
-            // This is the relevant special folder for the given scope plus IsolatedStorageDirectoryName.
-            // It is meant to replicate the behavior of the VM ComIsolatedStorage::GetRootDir().
-
-            // (note that Silverlight used "CoreIsolatedStorage" for a directory name and did not support machine scope)
-
-            Environment.SpecialFolder specialFolder =
-            IsMachine(scope) ? Environment.SpecialFolder.CommonApplicationData : // e.g. C:\ProgramData
-            IsRoaming(scope) ? Environment.SpecialFolder.ApplicationData : // e.g. C:\Users\Joe\AppData\Roaming
-            Environment.SpecialFolder.LocalApplicationData; // e.g. C:\Users\Joe\AppData\Local
-
-            string dataDirectory = Environment.GetFolderPath(specialFolder, Environment.SpecialFolderOption.Create);
-            dataDirectory = Path.Combine(dataDirectory, IsolatedStorageDirectoryName);
-
-            return dataDirectory;
-        }
-
-        [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file",
-            Justification = "Code handles single-file deployment by using the information of the .exe file")]
-        internal static void GetDefaultIdentityAndHash(out object identity, out string hash, char separator)
-        {
-            // In .NET Framework IsolatedStorage uses identity from System.Security.Policy.Evidence to build
-            // the folder structure on disk. It would use the "best" available evidence in this order:
-            //
-            //  1. Publisher (Authenticode)
-            //  2. StrongName
-            //  3. Url (CodeBase)
-            //  4. Site
-            //  5. Zone
-            //
-            // For .NET Core StrongName and Url are the only relevant types. By default evidence for the Domain comes
-            // from the Assembly which comes from the EntryAssembly(). We'll emulate the legacy default behavior
-            // by pulling directly from EntryAssembly.
-            //
-            // Note that it is possible that there won't be an EntryAssembly, which is something the .NET Framework doesn't
-            // have to deal with and isn't likely on .NET Core due to a single AppDomain. The exception is Android which
-            // doesn't set an EntryAssembly.
-
-            Assembly? assembly = Assembly.GetEntryAssembly();
-            string? location = null;
-
-            if (assembly != null)
-            {
-                AssemblyName assemblyName = assembly.GetName();
-
-                hash = IdentityHelper.GetNormalizedStrongNameHash(assemblyName)!;
-                if (hash != null)
-                {
-                    hash = string.Concat("StrongName", new ReadOnlySpan<char>(in separator), hash);
-                    identity = assemblyName;
-                    return;
-                }
-                else
-                {
-                    location = assembly.Location;
-                }
-            }
-
-            // In case of SingleFile deployment, Assembly.Location is empty. On Android there is no entry assembly.
-            if (string.IsNullOrEmpty(location))
-                location = Environment.ProcessPath;
-            if (string.IsNullOrEmpty(location))
-                throw new IsolatedStorageException(SR.IsolatedStorage_Init);
-            Uri locationUri = new Uri(location);
-            hash = string.Concat("Url", new ReadOnlySpan<char>(in separator), IdentityHelper.GetNormalizedUriHash(locationUri));
-            identity = locationUri;
-        }
-
-        internal static string GetRandomDirectory(string rootDirectory, IsolatedStorageScope scope)
-        {
-            string? randomDirectory = GetExistingRandomDirectory(rootDirectory);
-            if (string.IsNullOrEmpty(randomDirectory))
-            {
-                using (Mutex m = CreateMutexNotOwned(rootDirectory))
-                {
-                    if (!m.WaitOne())
-                    {
-                        throw new IsolatedStorageException(SR.IsolatedStorage_Init);
-                    }
-
-                    try
-                    {
-                        randomDirectory = GetExistingRandomDirectory(rootDirectory);
-                        if (string.IsNullOrEmpty(randomDirectory))
-                        {
-                            // Someone else hasn't created the directory before we took the lock
-                            randomDirectory = Path.Combine(rootDirectory, Path.GetRandomFileName(), Path.GetRandomFileName());
-                            CreateDirectory(randomDirectory, scope);
-                        }
-                    }
-                    finally
-                    {
-                        m.ReleaseMutex();
-                    }
-                }
-            }
-
-            return randomDirectory;
-        }
-
-        internal static string? GetExistingRandomDirectory(string rootDirectory)
-        {
-            // Look for an existing random directory at the given root
-            // (a set of nested directories that were created via Path.GetRandomFileName())
-
-            // Older versions of the .NET Framework created longer (24 character) random paths and would
-            // migrate them if they could not find the new style directory.
-
-            if (!Directory.Exists(rootDirectory))
-                return null;
-
-            foreach (string directory in Directory.GetDirectories(rootDirectory))
-            {
-                if (Path.GetFileName(directory)?.Length == 12)
-                {
-                    foreach (string subdirectory in Directory.GetDirectories(directory))
-                    {
-                        if (Path.GetFileName(subdirectory)?.Length == 12)
-                        {
-                            return subdirectory;
-                        }
-                    }
-                }
-            }
-
-            return null;
-        }
-
-        private static Mutex CreateMutexNotOwned(string pathName)
-        {
-            return new Mutex(initiallyOwned: false, name: @"Global\" + IdentityHelper.GetStrongHashSuitableForObjectName(pathName));
-        }
-    }
-}
index 984eeed..7e15f77 100644 (file)
@@ -1,6 +1,11 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Security;
+using System.Threading;
+
 namespace System.IO.IsolatedStorage
 {
     internal static partial class Helper
@@ -11,16 +16,16 @@ namespace System.IO.IsolatedStorage
 
         /// <summary>
         /// The full root directory is the relevant special folder from Environment.GetFolderPath() plus IsolatedStorageDirectoryName
-        /// and a set of random directory names if not roaming. (The random directories aren't created for WinRT as
-        /// the FolderPath locations for WinRT are app isolated already.)
+        /// and a set of random directory names if not roaming. (The random directories aren't created for Android/iOS as
+        /// the FolderPath locations for Android/iOS are app isolated already.)
         ///
         /// Examples:
         ///
         ///     User: @"C:\Users\jerem\AppData\Local\IsolatedStorage\10v31ho4.bo2\eeolfu22.f2w\"
         ///     User|Roaming: @"C:\Users\jerem\AppData\Roaming\IsolatedStorage\"
         ///     Machine: @"C:\ProgramData\IsolatedStorage\nin03cyc.wr0\o3j0urs3.0sn\"
-        ///     Android path: "/data/user/0/net.dot.System.IO.IsolatedStorage.Tests/files/.config/.isolated-storage/"
-        ///     iOS path: "/var/mobile/Containers/Data/Application/A323CBB9-A2B3-4432-9449-48CC20C07A7D/Documents/.config/.isolated-storage/"
+        ///     Android path: "/data/user/0/{packageName}/files/.config/.isolated-storage"
+        ///     iOS path: "/var/mobile/Containers/Data/Application/A323CBB9-A2B3-4432-9449-48CC20C07A7D/Documents/.config/.isolated-storage"
         ///
         /// Identity for the current store gets tacked on after this.
         /// </summary>
@@ -50,6 +55,85 @@ namespace System.IO.IsolatedStorage
             return s_userRootDirectory;
         }
 
+        internal static string? GetExistingRandomDirectory(string rootDirectory)
+        {
+            // Look for an existing random directory at the given root
+            // (a set of nested directories that were created via Path.GetRandomFileName())
+
+            // Older versions of the .NET Framework created longer (24 character) random paths and would
+            // migrate them if they could not find the new style directory.
+
+            if (!Directory.Exists(rootDirectory))
+                return null;
+
+            foreach (string directory in Directory.GetDirectories(rootDirectory))
+            {
+                if (Path.GetFileName(directory)?.Length == 12)
+                {
+                    foreach (string subdirectory in Directory.GetDirectories(directory))
+                    {
+                        if (Path.GetFileName(subdirectory)?.Length == 12)
+                        {
+                            return subdirectory;
+                        }
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file",
+            Justification = "Code handles single-file deployment by using the information of the .exe file")]
+        internal static void GetDefaultIdentityAndHash(out object identity, out string hash, char separator)
+        {
+            // In .NET Framework IsolatedStorage uses identity from System.Security.Policy.Evidence to build
+            // the folder structure on disk. It would use the "best" available evidence in this order:
+            //
+            //  1. Publisher (Authenticode)
+            //  2. StrongName
+            //  3. Url (CodeBase)
+            //  4. Site
+            //  5. Zone
+            //
+            // For .NET Core StrongName and Url are the only relevant types. By default evidence for the Domain comes
+            // from the Assembly which comes from the EntryAssembly(). We'll emulate the legacy default behavior
+            // by pulling directly from EntryAssembly.
+            //
+            // Note that it is possible that there won't be an EntryAssembly, which is something the .NET Framework doesn't
+            // have to deal with and isn't likely on .NET Core due to a single AppDomain. The exception is Android which
+            // doesn't set an EntryAssembly.
+
+            Assembly? assembly = Assembly.GetEntryAssembly();
+            string? location = null;
+
+            if (assembly != null)
+            {
+                AssemblyName assemblyName = assembly.GetName();
+
+                hash = IdentityHelper.GetNormalizedStrongNameHash(assemblyName)!;
+                if (hash != null)
+                {
+                    hash = string.Concat("StrongName", new ReadOnlySpan<char>(in separator), hash);
+                    identity = assemblyName;
+                    return;
+                }
+                else
+                {
+                    location = assembly.Location;
+                }
+            }
+
+            // In case of SingleFile deployment, Assembly.Location is empty. On Android there is no entry assembly.
+            if (string.IsNullOrEmpty(location))
+                location = Environment.ProcessPath;
+            if (string.IsNullOrEmpty(location))
+                throw new IsolatedStorageException(SR.IsolatedStorage_Init);
+            Uri locationUri = new Uri(location);
+            hash = string.Concat("Url", new ReadOnlySpan<char>(in separator), IdentityHelper.GetNormalizedUriHash(locationUri));
+            identity = locationUri;
+        }
+
         internal static bool IsMachine(IsolatedStorageScope scope) => ((scope & IsolatedStorageScope.Machine) != 0);
         internal static bool IsAssembly(IsolatedStorageScope scope) => ((scope & IsolatedStorageScope.Assembly) != 0);
         internal static bool IsApplication(IsolatedStorageScope scope) => ((scope & IsolatedStorageScope.Application) != 0);
index 69a2f70..ffc29df 100644 (file)
@@ -1,13 +1,63 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Text;
+
 namespace System.IO.IsolatedStorage
 {
     public sealed partial class IsolatedStorageFile : IsolatedStorage, IDisposable
     {
+        internal IsolatedStorageFile(IsolatedStorageScope scope)
+        {
+            // In legacy Xamarin root ends with "/.isolated-storage"
+            // In .Net 7 we added at the end  "/AppFiles" or "/Files" or "/AssemFiles/".
+            // e.g. .Net 7  path = /data/user/0/{packageName}/files/.isolated-storage/{hash}/{hash}/AppFiles/
+            // e.g. Xamarin path = /data/user/0/{packageName}/files/.config/.isolated-storage"
+            //
+            // Since we shipped that behavior as part of .NET 7 we can't change this now or upgraded apps wouldn't find their files anymore.
+            // We need to look for an existing directory first before using the legacy Xamarin approach.
+
+            InitStore(scope, null, null);
+
+            StringBuilder sb = new StringBuilder(GetIsolatedStorageRoot());
+
+            string directoryPath = sb.ToString() + SeparatorExternal;
+            if (Helper.IsApplication(scope) && Directory.Exists(directoryPath + s_appFiles))
+            {
+                sb.Append(SeparatorExternal);
+                sb.Append(s_appFiles);
+                sb.Append(SeparatorExternal);
+            }
+            else if (Helper.IsDomain(scope) && Directory.Exists(directoryPath + s_files))
+            {
+                sb.Append(SeparatorExternal);
+                sb.Append(s_files);
+                sb.Append(SeparatorExternal);
+            }
+            else if (Directory.Exists(directoryPath + s_assemFiles))
+            {
+                sb.Append(SeparatorExternal);
+                sb.Append(s_assemFiles);
+                sb.Append(SeparatorExternal);
+            }
+
+            _rootDirectory = sb.ToString();
+            Helper.CreateDirectory(_rootDirectory, scope);
+        }
+
         private string GetIsolatedStorageRoot()
         {
             return Helper.GetRootDirectory(Scope);
         }
+
+        private bool IsMatchingScopeDirectory(string _)
+        {
+            return (Helper.IsApplication(Scope)) || (Helper.IsAssembly(Scope)) || (Helper.IsDomain(Scope));
+        }
+
+        private string? GetParentDirectory()
+        {
+            return Path.GetDirectoryName(RootDirectory);
+        }
     }
 }
index 4f547d5..57da068 100644 (file)
@@ -7,6 +7,49 @@ namespace System.IO.IsolatedStorage
 {
     public sealed partial class IsolatedStorageFile : IsolatedStorage, IDisposable
     {
+        // Data file notes
+        // ===============
+
+        // "identity.dat" is the serialized identity object, such as StrongName or Url. It is used to
+        // enumerate stores, which we currently do not support.
+        //
+        // private const string IDFile = "identity.dat";
+
+        // "info.dat" is used to track disk space usage (against quota). The accounting file for Silverlight
+        // stores is "appInfo.dat". .NET Core is always in full trust so we can safely ignore these.
+        //
+        // private const string InfoFile = "info.dat";
+        // private const string AppInfoFile = "appInfo.dat";
+
+        internal IsolatedStorageFile(IsolatedStorageScope scope)
+        {
+            // Evidence isn't currently available: https://github.com/dotnet/runtime/issues/18208
+            // public static IsolatedStorageFile GetStore(IsolatedStorageScope scope, Evidence domainEvidence, Type domainEvidenceType, Evidence assemblyEvidence, Type assemblyEvidenceType) { return default(IsolatedStorageFile); }
+
+            // InitStore will set up the IdentityHash
+            InitStore(scope, null, null);
+
+            StringBuilder sb = new StringBuilder(GetIsolatedStorageRoot());
+            sb.Append(SeparatorExternal);
+
+            if (Helper.IsApplication(scope))
+            {
+                sb.Append(s_appFiles);
+            }
+            else if (Helper.IsDomain(scope))
+            {
+                sb.Append(s_files);
+            }
+            else
+            {
+                sb.Append(s_assemFiles);
+            }
+            sb.Append(SeparatorExternal);
+
+            _rootDirectory = sb.ToString();
+            Helper.CreateDirectory(_rootDirectory, scope);
+        }
+
         private string GetIsolatedStorageRoot()
         {
             StringBuilder root = new StringBuilder(Helper.GetRootDirectory(Scope));
@@ -15,5 +58,20 @@ namespace System.IO.IsolatedStorage
 
             return root.ToString();
         }
+
+        private bool IsMatchingScopeDirectory(string directory)
+        {
+            string directoryName = Path.GetFileName(directory);
+
+            return
+                (Helper.IsApplication(Scope) && string.Equals(directoryName, s_appFiles, StringComparison.Ordinal))
+                || (Helper.IsAssembly(Scope) && string.Equals(directoryName, s_assemFiles, StringComparison.Ordinal))
+                || (Helper.IsDomain(Scope) && string.Equals(directoryName, s_files, StringComparison.Ordinal));
+        }
+
+        private string? GetParentDirectory()
+        {
+            return Path.GetDirectoryName(RootDirectory.TrimEnd(Path.DirectorySeparatorChar));
+        }
     }
 }
index 4afd646..c787c69 100644 (file)
@@ -21,49 +21,6 @@ namespace System.IO.IsolatedStorage
         private readonly object _internalLock = new object();
         private readonly string _rootDirectory;
 
-        // Data file notes
-        // ===============
-
-        // "identity.dat" is the serialized identity object, such as StrongName or Url. It is used to
-        // enumerate stores, which we currently do not support.
-        //
-        // private const string IDFile = "identity.dat";
-
-        // "info.dat" is used to track disk space usage (against quota). The accounting file for Silverlight
-        // stores is "appInfo.dat". .NET Core is always in full trust so we can safely ignore these.
-        //
-        // private const string InfoFile = "info.dat";
-        // private const string AppInfoFile = "appInfo.dat";
-
-        internal IsolatedStorageFile(IsolatedStorageScope scope)
-        {
-            // Evidence isn't currently available: https://github.com/dotnet/runtime/issues/18208
-            // public static IsolatedStorageFile GetStore(IsolatedStorageScope scope, Evidence domainEvidence, Type domainEvidenceType, Evidence assemblyEvidence, Type assemblyEvidenceType) { return default(IsolatedStorageFile); }
-
-            // InitStore will set up the IdentityHash
-            InitStore(scope, null, null);
-
-            StringBuilder sb = new StringBuilder(GetIsolatedStorageRoot());
-            sb.Append(SeparatorExternal);
-
-            if (Helper.IsApplication(scope))
-            {
-                sb.Append(s_appFiles);
-            }
-            else if (Helper.IsDomain(scope))
-            {
-                sb.Append(s_files);
-            }
-            else
-            {
-                sb.Append(s_assemFiles);
-            }
-            sb.Append(SeparatorExternal);
-
-            _rootDirectory = sb.ToString();
-            Helper.CreateDirectory(_rootDirectory, scope);
-        }
-
         // Using this property to match .NET Framework for testing
         private string RootDirectory
         {
@@ -648,7 +605,7 @@ namespace System.IO.IsolatedStorage
 
             Close();
 
-            string? parentDirectory = Path.GetDirectoryName(RootDirectory.TrimEnd(Path.DirectorySeparatorChar));
+            string? parentDirectory = GetParentDirectory();
             Debug.Assert(parentDirectory != null);
 
             if (ContainsUnknownFiles(parentDirectory))
@@ -756,16 +713,6 @@ namespace System.IO.IsolatedStorage
                 );
         }
 
-        private bool IsMatchingScopeDirectory(string directory)
-        {
-            string directoryName = Path.GetFileName(directory);
-
-            return
-                (Helper.IsApplication(Scope) && string.Equals(directoryName, s_appFiles, StringComparison.Ordinal))
-                || (Helper.IsAssembly(Scope) && string.Equals(directoryName, s_assemFiles, StringComparison.Ordinal))
-                || (Helper.IsDomain(Scope) && string.Equals(directoryName, s_files, StringComparison.Ordinal));
-        }
-
         private static bool IsIdFile(string file) => string.Equals(Path.GetFileName(file), "identity.dat");
 
         private static bool IsInfoFile(string file) => string.Equals(Path.GetFileName(file), "info.dat");
index 8bfc91c..b14e54d 100644 (file)
@@ -9,8 +9,6 @@
     <Compile Include="System\IO\IsolatedStorage\IsolatedStorageFileStreamTests.cs" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="..\src\System\IO\IsolatedStorage\Helper.Win32Unix.cs"
-             Link="Internals\Helper.Win32Unix.cs" />
     <Compile Include="System\IO\IsolatedStorage\HelperTests.Win32Unix.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
    <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'android' or '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'maccatalyst' or '$(TargetPlatformIdentifier)' == 'tvos'">
     <Compile Include="..\src\System\IO\IsolatedStorage\Helper.AnyMobile.cs" />
     <Compile Include="System\IO\IsolatedStorage\TestHelper.AnyMobile.cs" />
+    <Compile Include="System\IO\IsolatedStorage\HelperTests.AnyMobile.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'android' and '$(TargetPlatformIdentifier)' != 'ios' and '$(TargetPlatformIdentifier)' != 'maccatalyst' and '$(TargetPlatformIdentifier)' != 'tvos' and '$(TargetPlatformIdentifier)' != ''">
     <Compile Include="..\src\System\IO\IsolatedStorage\Helper.NonMobile.cs" />
     <Compile Include="System\IO\IsolatedStorage\TestHelper.NonMobile.cs" />
+    <Compile Include="System\IO\IsolatedStorage\HelperTests.NonMobile.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(LibrariesProjectRoot)System.DirectoryServices\src\System.DirectoryServices.csproj" />
index 9758152..8c1980f 100644 (file)
@@ -64,6 +64,7 @@ namespace System.IO.IsolatedStorage
         }
 
         [Theory, MemberData(nameof(ValidStores))]
+        [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "For mobile platforms root ends with /.isolated-storage/")]
         public void ContainsUnknownFiles_NotOkDirectory(PresetScopes scope)
         {
             TestHelper.WipeStores();
index 23df212..d257b08 100644 (file)
@@ -63,7 +63,7 @@ namespace System.IO.IsolatedStorage
             using (var isf = GetPresetScope(scope))
             {
                 string root = isf.GetUserRootDirectory();
-                string file = "FileExists_Existence";
+                string file = scope.ToString() + "FileExists_Existence";
                 isf.CreateTestFile(file);
 
                 Assert.True(File.Exists(Path.Combine(root, file)), "exists per file.io where expected");
index a4a9582..b841fd5 100644 (file)
@@ -48,6 +48,7 @@ namespace System.IO.IsolatedStorage
         }
 
         [Fact]
+        [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "For mobile platforms root ends with /.isolated-storage")]
         public void GetUserStoreForApplication()
         {
             var isf = IsolatedStorageFile.GetUserStoreForApplication();
@@ -62,6 +63,7 @@ namespace System.IO.IsolatedStorage
         }
 
         [Fact]
+        [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "For mobile platforms root ends with /.isolated-storage")]
         public void GetUserStoreForAssembly()
         {
             var isf = IsolatedStorageFile.GetUserStoreForAssembly();
@@ -71,6 +73,7 @@ namespace System.IO.IsolatedStorage
         }
 
         [Fact]
+        [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "For mobile platforms root ends with /.isolated-storage")]
         public void GetUserStoreForDomain()
         {
             var isf = IsolatedStorageFile.GetUserStoreForDomain();
@@ -89,6 +92,7 @@ namespace System.IO.IsolatedStorage
         }
 
         [Fact]
+        [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst, "For mobile platforms root ends with /.isolated-storage")]
         public void GetStore_NullParamsAllowed()
         {
             VerifyApplicationStore(IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Application, (Type)null));
diff --git a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.AnyMobile.cs b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.AnyMobile.cs
new file mode 100644 (file)
index 0000000..baca9b5
--- /dev/null
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+using System.Reflection;
+using System.Text;
+
+namespace System.IO.IsolatedStorage
+{
+    public partial class HelperTests
+    {                
+        [Theory, InlineData(IsolatedStorageScope.User)]
+        public void GetRandomDirectory(IsolatedStorageScope scope)
+        {
+            using (var temp = new TempDirectory())
+            {
+                string randomDir = Helper.GetRandomDirectory(temp.Path, scope);
+                Assert.True(Directory.Exists(randomDir.Replace(Helper.IsolatedStorageDirectoryName, "")));
+            }
+        }
+
+        [Theory,
+            InlineData(IsolatedStorageScope.Assembly),
+            InlineData(IsolatedStorageScope.Assembly | IsolatedStorageScope.Roaming),
+            InlineData(IsolatedStorageScope.User)
+            ]
+        public void GetRandomDirectoryWithExistingDir(IsolatedStorageScope scope)
+        {
+            using (var temp = new TempDirectory())
+            {
+                Assert.Null(Helper.GetExistingRandomDirectory(temp.Path));
+
+                string randomPath = Path.Combine(temp.Path, Path.GetRandomFileName(), Path.GetRandomFileName());
+                Directory.CreateDirectory(randomPath);
+                Assert.Equal(randomPath, Helper.GetExistingRandomDirectory(temp.Path));
+                Assert.Equal(Helper.GetRandomDirectory(temp.Path, scope), Helper.GetExistingRandomDirectory(temp.Path));
+            }
+        }
+
+        [Theory,
+            InlineData(IsolatedStorageScope.Assembly),
+            InlineData(IsolatedStorageScope.Assembly | IsolatedStorageScope.Roaming),
+            InlineData(IsolatedStorageScope.User)
+            ]
+        public void GetRandomDirectoryWithNotExistingDir(IsolatedStorageScope scope)
+        {
+            using (var temp = new TempDirectory())
+            {
+                Assert.Null(Helper.GetExistingRandomDirectory(temp.Path));  
+                Assert.Equal(Helper.GetRandomDirectory(Helper.GetDataDirectory(scope), scope), Helper.GetDataDirectory(scope));
+            }
+        }        
+
+        [Fact]
+        public void GetUserStoreForApplicationPath()
+        {
+            TestHelper.WipeStores();
+
+            using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
+            {
+                string root = isf.GetUserRootDirectory();
+                Assert.EndsWith("/.config/.isolated-storage", root);
+            }
+        }
+
+        [Fact]
+        public void GetUserStoreForAssemblyPath()
+        {
+            TestHelper.WipeStores();
+
+            using (var isf = IsolatedStorageFile.GetUserStoreForAssembly())
+            {
+                string root = isf.GetUserRootDirectory();
+                Assert.EndsWith("/.config/.isolated-storage", root);               
+            }
+        }
+
+        [Fact]
+        public void GetUserStoreForDomainPath()
+        {
+            TestHelper.WipeStores();
+
+            using (var isf = IsolatedStorageFile.GetUserStoreForDomain())
+            {
+                string root = isf.GetUserRootDirectory();
+                Assert.EndsWith("/.config/.isolated-storage", root);                
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.NonMobile.cs b/src/libraries/System.IO.IsolatedStorage/tests/System/IO/IsolatedStorage/HelperTests.NonMobile.cs
new file mode 100644 (file)
index 0000000..d6e5851
--- /dev/null
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.IO.IsolatedStorage
+{
+    public partial class HelperTests
+    {        
+        [Theory,
+            InlineData(IsolatedStorageScope.User),
+            InlineData(IsolatedStorageScope.Machine),
+            ]
+        public void GetRandomDirectory(IsolatedStorageScope scope)
+        {
+            using (var temp = new TempDirectory())
+            {
+                string randomDir = Helper.GetRandomDirectory(temp.Path, scope);
+                Assert.True(Directory.Exists(randomDir));
+            }
+        }       
+    }
+}
index 649de06..78dab06 100644 (file)
@@ -19,18 +19,5 @@ namespace System.IO.IsolatedStorage
                 Assert.Equal(randomPath, Helper.GetExistingRandomDirectory(temp.Path));
             }
         }
-
-        [Theory,
-            InlineData(IsolatedStorageScope.User),
-            InlineData(IsolatedStorageScope.Machine),
-            ]
-        public void GetRandomDirectory(IsolatedStorageScope scope)
-        {
-            using (var temp = new TempDirectory())
-            {
-                string randomDir = Helper.GetRandomDirectory(temp.Path, scope);
-                Assert.True(Directory.Exists(randomDir));
-            }
-        }
     }
 }
index 63709fd..d7c4aab 100644 (file)
@@ -15,9 +15,15 @@ namespace System.IO.IsolatedStorage
             string randomUserRoot = Helper.GetRandomDirectory(userRoot, IsolatedStorageScope.User);
             roots.Add(randomUserRoot);
 
-            // Application scope doesn't go under a random dir
-            roots.Add(userRoot);
             return roots;
         }
+
+        /// <summary>
+        /// The actual root of the store (housekeeping files are kept here in NetFX)
+        /// </summary>
+        public static string GetIdentityRootDirectory(this IsolatedStorageFile isf)
+        {
+            return isf.GetUserRootDirectory();
+        }
     }
 }
index e0217dc..52914bf 100644 (file)
@@ -31,6 +31,14 @@ namespace System.IO.IsolatedStorage
 
             return roots;
         }
+
+        /// <summary>
+        /// The actual root of the store (housekeeping files are kept here in NetFX)
+        /// </summary>
+        public static string GetIdentityRootDirectory(this IsolatedStorageFile isf)
+        {
+            return Path.GetDirectoryName(isf.GetUserRootDirectory().TrimEnd(Path.DirectorySeparatorChar));           
+        }
     }
 }
     
\ No newline at end of file
index 0633950..820174e 100644 (file)
@@ -32,13 +32,6 @@ namespace System.IO.IsolatedStorage
             return (string)s_rootDirectoryProperty.GetValue(isf);
         }
 
-        /// <summary>
-        /// The actual root of the store (housekeeping files are kept here in NetFX)
-        /// </summary>
-        public static string GetIdentityRootDirectory(this IsolatedStorageFile isf)
-        {
-            return Path.GetDirectoryName(isf.GetUserRootDirectory().TrimEnd(Path.DirectorySeparatorChar));
-        }
 
         /// <summary>
         /// Simple wrapper to create the given file (and close the handle)