diff options
author | Sebastien Pouliot <sebastien@ximian.com> | 2009-04-16 19:25:45 +0400 |
---|---|---|
committer | Sebastien Pouliot <sebastien@ximian.com> | 2009-04-16 19:25:45 +0400 |
commit | 52531b6daab1f92ac7144196f120c74ff42614c6 (patch) | |
tree | 73f80898e24852d8e2ba47cee57978bb8d8c8651 /mcs/class/corlib/System.IO.IsolatedStorage | |
parent | bc211b721b6a2181ad692e7b9fcb1e465593f457 (diff) |
In .:
2009-04-16 Sebastien Pouliot <sebastien@ximian.com>
* corlib.dll.sources: Add System.IO.IsolatedStorage/
MoonIsolatedStorage.cs to the build
In System.IO.IsolatedStorage:
2009-04-16 Sebastien Pouliot <sebastien@ximian.com>
* MoonIsolatedStorage.cs: New. Manage the shared (location, quota)
information about Moonlight isolated storage.
* MoonIsolatedStorageFile.cs: Adjust with new MoonIsolatedStorage.
Implement IncreaseQuotaTo UI.
* MoonIsolatedStorageFileStream.cs: Adjust with MoonIsolatedStorage.
svn path=/trunk/mcs/; revision=131903
Diffstat (limited to 'mcs/class/corlib/System.IO.IsolatedStorage')
4 files changed, 286 insertions, 140 deletions
diff --git a/mcs/class/corlib/System.IO.IsolatedStorage/ChangeLog b/mcs/class/corlib/System.IO.IsolatedStorage/ChangeLog index e7b4e66d54f..d69ec409887 100644 --- a/mcs/class/corlib/System.IO.IsolatedStorage/ChangeLog +++ b/mcs/class/corlib/System.IO.IsolatedStorage/ChangeLog @@ -1,3 +1,11 @@ +2009-04-16 Sebastien Pouliot <sebastien@ximian.com> + + * MoonIsolatedStorage.cs: New. Manage the shared (location, quota) + information about Moonlight isolated storage. + * MoonIsolatedStorageFile.cs: Adjust with new MoonIsolatedStorage. + Implement IncreaseQuotaTo UI. + * MoonIsolatedStorageFileStream.cs: Adjust with MoonIsolatedStorage. + 2009-04-16 Sebastien Pouliot <sebastien@ximian.com> * IsolatedStorageFile.cs: Fix thread-safety issue at creation time. diff --git a/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorage.cs b/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorage.cs new file mode 100644 index 00000000000..184137eafcb --- /dev/null +++ b/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorage.cs @@ -0,0 +1,232 @@ +// +// System.IO.IsolatedStorage.IsolatedQuotaGroup +// +// Authors +// Sebastien Pouliot <sebastien@ximian.com> +// +// Copyright (C) 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 +// "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. +// + +#if NET_2_1 + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Cryptography; +using System.Text; + +namespace System.IO.IsolatedStorage { + + static class IsolatedStorage { + + // NOTE: both the 'site' and 'application' share the same quota + internal const long DefaultQuota = 1024 * 1024; + // Since we can extend more than AvailableFreeSize we need to substract the "safety" value out of it + private const int SafetyZone = 1024; + + + static string site_root; + static string site_config; + static long site_quota; + + // this is similar to Silverlight hierarchy because differing too much would cause + // problems with the 260 character maximum allowed for paths + + // Considering a 10 characters user name the following platform will allow: + // 109 characters path, under Windows Vista (Silverlight 2) + // 77 characters path, under Windows XP (Silverlight 2) + // 100 characters path, under Mac OSX (Silverlight 2) + // 159 characters path, under Linux (Moonlight 2) + + // 1234567890123456789012345678901234567890123 = 43 + 28 + 1 + 28 +1 = 101 + // 1 2 3 4 + // /home/1234567890/.local/share/moonlight/is/{site-hash:28}/{app-hash:28}/ + + static IsolatedStorage () + { + string isolated_root = GetIsolatedStorageRoot (); + // enable/disable osilated storage - requires restart + Enabled = !File.Exists (Path.Combine (isolated_root, "disabled")); + + // from System.Windows.Application we made "xap_uri" correspond to + // Application.Current.Host.Source.AbsoluteUri + string app = (AppDomain.CurrentDomain.GetData ("xap_uri") as string); + if (app.StartsWith ("file://")) { + // every path is a different site, the XAP is the application + Site = Path.GetDirectoryName (app.Substring (7)); + } else { + // for http[s] the "Site Identity" is built using: + // * the protocol (so http and https are different); + // * the host (so beta.moonlight.com is different from www.moonlight.com); and + // * the port (so 8080 is different from 80) but + // ** 80 and none are identical for HTTP + // ** 443 and none are identical for HTTPS + Site = app.Substring (0, app.IndexOf ('/', 8)); + } + + // the "Site Identity" + string site_hash = Hash (Site); + site_root = TryDirectory (Path.Combine (isolated_root, site_hash)); + SetupSite (site_root); + SitePath = TryDirectory (Path.Combine (site_root, site_hash)); + + // the "Application Identity" + string app_hash = Hash (app); + SetupApplication (app, app_hash, site_root); + ApplicationPath = TryDirectory (Path.Combine (site_root, app_hash)); + } + + static string GetIsolatedStorageRoot () + { + // http://freedesktop.org/Standards/basedir-spec/basedir-spec-0.6.html + string xdg_data_home = Environment.GetEnvironmentVariable ("XDG_DATA_HOME"); + if (String.IsNullOrEmpty (xdg_data_home)) { + xdg_data_home = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); + } + + string moonlight = TryDirectory (Path.Combine (xdg_data_home, "moonlight")); + return TryDirectory (Path.Combine (moonlight, "is")); + } + + static void SetupSite (string dir) + { + site_quota = DefaultQuota; + // read configuration file (e.g. quota) if it exists, otherwise write it + site_config = Path.Combine (dir, "config"); + if (File.Exists (site_config)) { + LoadConfiguration (); + } else { + SaveConfiguration (); + } + } + + static void LoadConfiguration () + { + // read quota, the rest is not useful to us + using (StreamReader sr = new StreamReader (site_config)) { + string line = sr.ReadLine (); + while (line != null) { + if (line.StartsWith ("QUOTA = ")) { + if (!Int64.TryParse (line.Substring (8, line.Length - 8), out site_quota)) + Quota = DefaultQuota; + } + line = sr.ReadLine (); + } + } + } + + static void SaveConfiguration () + { + using (StreamWriter sw = new StreamWriter (site_config)) { + sw.WriteLine ("URI = {0}", Site); + sw.WriteLine ("QUOTA = {0}", Quota); + } + } + + static void SetupApplication (string app, string app_hash, string dir) + { + // save the application information (for the management UI) + string config = Path.Combine (dir, app_hash + ".info"); + if (File.Exists (config)) + return; + + using (StreamWriter sw = new StreamWriter (config)) { + sw.WriteLine ("URI = {0}", app); + } + } + + // goal: uniform length directory name + // non-goal: security by obsfucation + static string Hash (string name) + { + string id; + using (SHA1Managed hash = new SHA1Managed ()) { + byte[] digest = hash.ComputeHash (Encoding.UTF8.GetBytes (name)); + id = Convert.ToBase64String (digest); + } + return (id.IndexOf ('/') == -1) ? id : id.Replace ('/', '-'); + } + + static string TryDirectory (string path) + { + try { + Directory.CreateDirectory (path); + return path; + } catch { + return null; + } + } + + static internal void Remove (string dir) + { + try { + Directory.Delete (dir, true); + } + finally { + TryDirectory (dir); + } + } + + static internal bool CanExtend (long request) + { + return (request <= AvailableFreeSpace + SafetyZone); + } + + static public string ApplicationPath { + get; private set; + } + + static public string SitePath { + get; private set; + } + + static public long AvailableFreeSpace { + get { return Quota - Current - SafetyZone; } + } + + [DllImport ("moon")] + extern static long isolated_storage_get_current_usage (string root); + + static public long Current { + get { return isolated_storage_get_current_usage (site_root); } + } + + static public long Quota { + get { return site_quota; } + set { + site_quota = value; + SaveConfiguration (); + } + } + + static public string Site { + get; private set; + } + + // it is possible, from the UI, to completely disable IsolatedStorage + static public bool Enabled { get; private set; } + } +} + +#endif + diff --git a/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFile.cs b/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFile.cs index 1fb01ea9e4a..a89798417ad 100644 --- a/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFile.cs +++ b/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFile.cs @@ -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 @@ -31,6 +31,7 @@ #if NET_2_1 using System; using System.IO; +using System.Runtime.InteropServices; using System.Security; namespace System.IO.IsolatedStorage { @@ -38,78 +39,27 @@ namespace System.IO.IsolatedStorage { // 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 - // E.g. another application on the same site (i.e. host) for a site store - - // TODO: use shared memory (and locks) to keep the quota and used values synchronized - - // TODO: we need to be initialized by Application (System.Windows.dll) to know the correct root directories - - // 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 + // 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 // 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 public sealed class IsolatedStorageFile : IDisposable { - private const long DefaultQuota = 1024 * 1024; - // 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 object locker = new object (); - - 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) - throw new IsolatedStorageException ("No root"); - - isolated_appdir = TryDirectory (Path.Combine (isolated_root, "application")); - isolated_sitedir = TryDirectory (Path.Combine (isolated_root, "site")); - } - + private string basedir; - private string datafile; - private long quota; private long used; private bool removed = false; private bool disposed = false; - internal IsolatedStorageFile (string root, string dirname) + internal IsolatedStorageFile (string root) { - string dir = Path.Combine (root, dirname); - basedir = TryDirectory (dir); - - datafile = Path.Combine (root, dirname + ".data"); - if (File.Exists (datafile)) { - ReadStoreData (); - } else { - used = 0; - quota = DefaultQuota; - UpdateStoreData (); - } + basedir = root; } internal void PreCheck () @@ -122,47 +72,29 @@ namespace System.IO.IsolatedStorage { public static IsolatedStorageFile GetUserStoreForApplication () { - if (isolated_appdir == null) - throw new SecurityException (); - - // from System.Windows.Application we made "xap_uri" correspond to - // Application.Current.Host.Source.AbsoluteUri - string app = (AppDomain.CurrentDomain.GetData ("xap_uri") as string); - if (app == null) - throw new SecurityException (); - - return new IsolatedStorageFile (isolated_appdir, app.Replace ("/", "%27")); + return new IsolatedStorageFile (IsolatedStorage.ApplicationPath); } public static IsolatedStorageFile GetUserStoreForSite () { - if (isolated_sitedir == null) - throw new SecurityException (); - - // from System.Windows.Application we made "xap_host" correspond to - // Application.Current.Host.Source.Host - string site = (AppDomain.CurrentDomain.GetData ("xap_host") as string); - if (site == null) - throw new SecurityException (); - // no host is defined for things like: file://home/... - if (site.Length == 0) - site = "localhost:file"; - - return new IsolatedStorageFile (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 - // ArgumentNullException for null paths // ArgumentException for invalid characters string combined = Path.Combine (basedir, path); try { string full = Path.GetFullPath (combined); - 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 (); @@ -171,7 +103,11 @@ namespace System.IO.IsolatedStorage { public void CreateDirectory (string dir) { PreCheck (); - Directory.CreateDirectory (Verify (dir)); + if (dir == null) + throw new ArgumentNullException ("dir"); + // empty dir is ignored + if (dir.Length > 0) + Directory.CreateDirectory (Verify (dir)); } public IsolatedStorageFileStream CreateFile (string path) @@ -189,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"); @@ -276,16 +216,8 @@ namespace System.IO.IsolatedStorage { public void Remove () { PreCheck (); - try { - Directory.Delete (basedir, true); - } - finally { - used = 0; - quota = DefaultQuota; - UpdateStoreData (); - 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 @@ -293,8 +225,7 @@ namespace System.IO.IsolatedStorage { public long AvailableFreeSpace { get { PreCheck (); - ReadStoreData (); - return quota - used - SafetyZone; + return IsolatedStorage.AvailableFreeSpace; } } @@ -303,57 +234,32 @@ namespace System.IO.IsolatedStorage { public long Quota { get { PreCheck (); - ReadStoreData (); - 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) + if (newQuotaSize <= Quota) throw new ArgumentException ("newQuotaSize", "Only increases are possible"); - // FIXME: we must ensure this is called from an event handler (or return false) - // we need to find out where it's valid (e.g. Page.Loaded and Page.MouseLeftButtonUp are not) - - // FIXME: need plugin UI to confirm the change - - return false; - } - - internal bool CanExtend (long request) - { - bool result = (request <= AvailableFreeSpace + SafetyZone); - if (result) { - lock (locker) { - used += request; - } - } + 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; } - - // TEMPORARY - not thread (or cross process) safe - - private byte [] data = new byte [16]; - - internal void ReadStoreData () - { - using (FileStream fs = new FileStream (datafile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - fs.Read (data, 0, 16); - quota = BitConverter.ToInt64 (data, 0); - used = BitConverter.ToInt64 (data, 8); - } - } - - internal void UpdateStoreData () - { - using (FileStream fs = new FileStream (datafile, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) { - fs.Write (BitConverter.GetBytes (quota), 0, 8); - fs.Write (BitConverter.GetBytes (used), 0, 8); - } - } } } #endif diff --git a/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFileStream.cs b/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFileStream.cs index e6bb71a3906..d0f9f11a7f4 100644 --- a/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFileStream.cs +++ b/mcs/class/corlib/System.IO.IsolatedStorage/MoonIsolatedStorageFileStream.cs @@ -114,7 +114,7 @@ namespace System.IO.IsolatedStorage { // will that request put us in a position to grow *or shrink* the file ? // note: this can be negative, e.g. calling SetLength(0), so we can't call EnsureQuotaLimits directly - if (!container.CanExtend (value - Length)) + if (!IsolatedStorage.CanExtend (value - Length)) throw new IsolatedStorageException ("Requested size is larger than remaining quota allowance."); base.SetLength (value); @@ -212,7 +212,7 @@ namespace System.IO.IsolatedStorage { if (grow < 0) return; - if (!container.CanExtend (grow)) + if (!IsolatedStorage.CanExtend (grow)) throw new IsolatedStorageException ("Requested size is larger than remaining quota allowance."); } } |