// 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 ()
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 ();
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);
}
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");
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));
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 ()
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)
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;
}
}
}