New tests.
[mono.git] / mcs / class / corlib / System.IO.IsolatedStorage / MoonIsolatedStorageFile.cs
index 58c484d3db877c97dbbbb34f957df7258e4c8dd5..0c0fe270416bd8036b89ad985dff5e382d012f1f 100644 (file)
@@ -7,7 +7,7 @@
 //      Miguel de Icaza (miguel@novell.com)
 //     Sebastien Pouliot  <sebastien@ximian.com>
 //
-// Copyright (C) 2007, 2008 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2007, 2008, 2009 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-#if NET_2_1
+#if MOONLIGHT
 using System;
 using System.IO;
+using System.Runtime.InteropServices;
 using System.Security;
 
 namespace System.IO.IsolatedStorage {
 
-       // FIXME: we need to tool to set quota (SL does this on a property page of the "Silverlight Configuration" menu)
-       // the beta2 user interface (that I only see in IE) does not seems to differentiate application and site storage
+       // Most of the time there will only be a single instance of both 
+       // * Application Store (GetUserStoreForApplication)
+       // * Site Store (GetUserStoreForSite)
+       // However both can have multiple concurrent uses, e.g.
+       // * another instance of the same application (same URL) running in another Moonlight instance
+       // * another application on the same site (i.e. host) for a site store
+       // and share the some quota, i.e. a site and all applications on the sites share the same space
 
-       public sealed class IsolatedStorageFile : IDisposable {
-
-               // Since we can extend more than AvailableFreeSize we need to substract the "safety" value out of it
-               private const int SafetyZone = 1024;
-
-               static string isolated_root;
-               static string isolated_appdir;
-               static string isolated_sitedir;
-               
-               static string TryDirectory (string path)
-               {
-                       try {
-                               Directory.CreateDirectory (path);
-                               return path;
-                       } catch {
-                               return null;
-                       }
-               }
-               
-               static IsolatedStorageFile ()
-               {
-                        string xdg_data_home = Environment.GetEnvironmentVariable ("XDG_DATA_HOME");
-                        if (String.IsNullOrEmpty (xdg_data_home)) {
-                                xdg_data_home = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData);
-                        }
-
-                       string isolated_root;
-                       isolated_root = TryDirectory (Path.Combine (xdg_data_home, "moonlight"));
-                       if (isolated_root == null){
-                               //
-                               // Maybe try a few others?
-                               //
-                               return;
-                       }
-
-                       // FIXME: we need to store quota (and any other config) outside the stores
-                       // - so they can't be modified by the application itself
-                       // - so it's not destroyed to Remove
+       // notes:
+       // * quota seems computed in (disk) blocks, i.e. a small file will have a (non-small) size
+       // e.g. every files and directories entries takes 1KB
 
-                       isolated_appdir = TryDirectory (Path.Combine (isolated_root, "application"));
-                       isolated_sitedir = TryDirectory (Path.Combine (isolated_root, "site"));
-               }
+       public sealed class IsolatedStorageFile : IDisposable {
 
+               static object locker = new object ();
+       
                private string basedir;
-               private long quota;
+               private long used;
                private bool removed = false;
                private bool disposed = false;
 
-               internal IsolatedStorageFile (string dir)
+               internal IsolatedStorageFile (string root)
                {
-                       basedir = TryDirectory (dir);
-                       // FIXME: we need to read the quota allocated to this storage
-                       quota = 1024 * 1024;
-                       // FIXME: we need to compute the available space for this storage (and keep it updated)
+                       basedir = root;
                }
                
                internal void PreCheck ()
@@ -104,33 +72,29 @@ namespace System.IO.IsolatedStorage {
 
                public static IsolatedStorageFile GetUserStoreForApplication ()
                {
-                       if (isolated_appdir == null)
-                               throw new SecurityException ();
-                       
-                       // FIXME: we need to construct something based on the application, like:
-                       // Application.Current.GetType ().FullName
-                       // of course this is outside corlib so we need prior notification of this
-                       return new IsolatedStorageFile (Path.Combine (isolated_appdir, "application"));
+                       return new IsolatedStorageFile (IsolatedStorage.ApplicationPath);
                }
 
                public static IsolatedStorageFile GetUserStoreForSite ()
                {
-                       if (isolated_sitedir == null)
-                               throw new SecurityException ();
-
-                       // FIXME: we need to construct something based on the site, like:
-                       // Application.Current.Host.Source.AbsoluteUri (or a subset of this)
-                       // of course this is outside corlib so we need prior notification of this
-                       return new IsolatedStorageFile (Path.Combine (isolated_sitedir, "site"));
+                       return new IsolatedStorageFile (IsolatedStorage.SitePath);
                }
 
                internal string Verify (string path)
                {
+                       // special case: 'path' would be returned (instead of combined)
+                       if ((path.Length > 0) && (path [0] == '/'))
+                               path = path.Substring (1, path.Length - 1);
+
+                       // outside of try/catch since we want to get things like
+                       //      ArgumentException for invalid characters
+                       string combined = Path.Combine (basedir, path);
                        try {
-                               string full = Path.GetFullPath (Path.Combine (basedir, path));
-                               if (full.StartsWith (basedir + Path.DirectorySeparatorChar))
+                               string full = Path.GetFullPath (combined);
+                               if (full.StartsWith (basedir))
                                        return full;
                        } catch {
+                               // we do not supply an inner exception since it could contains details about the path
                                throw new IsolatedStorageException ();
                        }
                        throw new IsolatedStorageException ();
@@ -138,17 +102,17 @@ namespace System.IO.IsolatedStorage {
                
                public void CreateDirectory (string dir)
                {
-                       Directory.CreateDirectory (Verify (dir));
+                       PreCheck ();
+                       if (dir == null)
+                               throw new ArgumentNullException ("dir");
+                       // empty dir is ignored
+                       if (dir.Length > 0)
+                               Directory.CreateDirectory (Verify (dir));
                }
 
                public IsolatedStorageFileStream CreateFile (string path)
                {
                        PreCheck ();
-                       if (path == null)
-                               throw new ArgumentNullException ("path");
-                       if (path.Length == 0)
-                               throw new ArgumentException ("path");
-
                        try {
                                return new IsolatedStorageFileStream (path, FileMode.Create, this);
                        }
@@ -161,12 +125,16 @@ namespace System.IO.IsolatedStorage {
                public void DeleteDirectory (string dir)
                {
                        PreCheck ();
+                       if (dir == null)
+                               throw new ArgumentNullException ("dir");
                        Directory.Delete (Verify (dir));
                }
 
                public void DeleteFile (string file)
                {
                        PreCheck ();
+                       if (file == null)
+                               throw new ArgumentNullException ("file");
                        string checked_filename = Verify (file);
                        if (!File.Exists (checked_filename))
                                throw new IsolatedStorageException ("File does not exists");
@@ -203,6 +171,16 @@ namespace System.IO.IsolatedStorage {
                        return paths;
                }
 
+               private void CheckSearchPattern (string searchPattern)
+               {
+                       if (searchPattern == null)
+                               throw new ArgumentNullException ("searchPattern");
+                       if (searchPattern.Length == 0)
+                               throw new IsolatedStorageException ("searchPattern");
+                       if (searchPattern.IndexOfAny (Path.GetInvalidPathChars ()) != -1)
+                               throw new ArgumentException ("searchPattern");
+               }
+
                public string [] GetDirectoryNames ()
                {
                        return HideAppDirs (Directory.GetDirectories (basedir));
@@ -210,10 +188,30 @@ namespace System.IO.IsolatedStorage {
 
                public string [] GetDirectoryNames (string searchPattern)
                {
-                       if (searchPattern.IndexOf ('/') != -1)
-                               throw new IsolatedStorageException ();
-                       
-                       return HideAppDirs (Directory.GetDirectories (basedir, searchPattern));
+                       CheckSearchPattern (searchPattern);
+
+                       // note: IsolatedStorageFile accept a "dir/file" pattern which is not allowed by DirectoryInfo
+                       // so we need to split them to get the right results
+                       string path = Path.GetDirectoryName (searchPattern);
+                       string pattern = Path.GetFileName (searchPattern);
+                       string [] afi = null;
+
+                       if (path == null || path.Length == 0) {
+                               return HideAppDirs (Directory.GetDirectories (basedir, searchPattern));
+                       } else {
+                               // we're looking for a single result, identical to path (no pattern here)
+                               // we're also looking for something under the current path (not outside isolated storage)
+
+                               string [] subdirs = Directory.GetDirectories (basedir, path);
+                               if (subdirs.Length != 1 || subdirs [0].IndexOf (basedir) < 0)
+                                       throw new IsolatedStorageException ();
+
+                               DirectoryInfo dir = new DirectoryInfo (subdirs [0]);
+                               if (dir.Name != path)
+                                       throw new IsolatedStorageException ();
+
+                               return GetNames (dir.GetDirectories (pattern));
+                       }
                }
 
                public string [] GetFileNames ()
@@ -223,10 +221,39 @@ namespace System.IO.IsolatedStorage {
 
                public string [] GetFileNames (string searchPattern)
                {
-                       if (searchPattern.IndexOf ('/') != -1)
-                               throw new IsolatedStorageException ();
-                       
-                       return HideAppDirs (Directory.GetFiles (basedir, searchPattern));
+                       CheckSearchPattern (searchPattern);
+
+                       // note: IsolatedStorageFile accept a "dir/file" pattern which is not allowed by DirectoryInfo
+                       // so we need to split them to get the right results
+                       string path = Path.GetDirectoryName (searchPattern);
+                       string pattern = Path.GetFileName (searchPattern);
+                       string [] afi = null;
+
+                       if (path == null || path.Length == 0) {
+                               return HideAppDirs (Directory.GetFiles (basedir, searchPattern));
+                       } else {
+                               // we're looking for a single result, identical to path (no pattern here)
+                               // we're also looking for something under the current path (not outside isolated storage)
+
+                               string [] subdirs = Directory.GetDirectories (basedir, path);
+                               if (subdirs.Length != 1 || subdirs [0].IndexOf (basedir) < 0)
+                                       throw new IsolatedStorageException ();
+
+                               DirectoryInfo dir = new DirectoryInfo (subdirs [0]);
+                               if (dir.Name != path)
+                                       throw new IsolatedStorageException ();
+
+                               return GetNames (dir.GetFiles (pattern));
+                       }
+               }
+
+               // Return the file name portion of a full path
+               private string[] GetNames (FileSystemInfo[] afsi)
+               {
+                       string[] r = new string[afsi.Length];
+                       for (int i = 0; i != afsi.Length; ++i)
+                               r[i] = afsi[i].Name;
+                       return r;
                }
 
                public IsolatedStorageFileStream OpenFile (string path, FileMode mode)
@@ -248,45 +275,49 @@ namespace System.IO.IsolatedStorage {
                public void Remove ()
                {
                        PreCheck ();
-                       try {
-                               Directory.Delete (basedir, true);
-                       }
-                       finally {
-                               TryDirectory (basedir);
-                               removed = true;
-                       }
+                       IsolatedStorage.Remove (basedir);
+                       removed = true;
                }
 
+               // note: available free space could be changed from another application (same URL, another ML instance) or
+               // another application on the same site
                public long AvailableFreeSpace {
                        get {
                                PreCheck ();
-                               // FIXME: compute real free space
-                               // then substract the safety
-                               return 1024*1024 - SafetyZone;
+                               return IsolatedStorage.AvailableFreeSpace;
                        }
                }
 
+               // note: quota could be changed from another application (same URL, another ML instance) or
+               // another application on the same site
                public long Quota {
                        get {
                                PreCheck ();
-                               return quota;
+                               return IsolatedStorage.Quota;
                        }
                }
 
+               [DllImport ("moon")]
+               [return: MarshalAs (UnmanagedType.Bool)]
+               extern static bool isolated_storage_increase_quota_to (string primary_text, string secondary_text);
+
+               const long mb = 1024 * 1024;
+
                public bool IncreaseQuotaTo (long newQuotaSize)
                {
                        PreCheck ();
-                       if (newQuotaSize <= Quota)
-                               throw new ArgumentException ("newQuotaSize", "Only increase is possible");
-
-                       // FIXME: save new quota limit to configuration
-                       quota = newQuotaSize;
-                       return true;
-               }
 
-               internal bool CanExtend (long request)
-               {
-                       return (request <= AvailableFreeSpace + SafetyZone);
+                       if (newQuotaSize <= Quota)
+                               throw new ArgumentException ("newQuotaSize", "Only increases are possible");
+
+                       string message = String.Format ("This web site, <u>{0}</u>, is requesting an increase of its local storage capacity on your computer. It is currently using <b>{1:F1} MB</b> out of a maximum of <b>{2:F1} MB</b>.",
+                               IsolatedStorage.Site, IsolatedStorage.Current / mb, IsolatedStorage.Quota / mb);
+                       string question = String.Format ("Do you want to increase the web site quota to a new maximum of <b>{0:F1} MB</b> ?", 
+                               newQuotaSize / mb);
+                       bool result = isolated_storage_increase_quota_to (message, question);
+                       if (result)
+                               IsolatedStorage.Quota = newQuotaSize;
+                       return result;
                }
        }
 }