Click here to Skip to main content
Click here to Skip to main content

IStorageFolder - Encapsulation for file-system folders and files

, 27 Feb 2006
Rate this:
Please Sign up or sign in to vote.
An interface that encapsulates file-system folders and files that includes implementations for the file system and memory.

Introduction

Have you ever written an application that:

  • Creates and uses small temp files and folders that you forget to clean up?
  • Needs to work with both the file system and the isolated storage?
  • Iterates through NTFS file streams?
  • Needs to hide data from the end-user without dirtying up their system?
  • Created a plug-in system and realized each one needed to manage their own set of paths?

I have. And I created the IStorageFolder interface and the accompanying implementation and helper classes to deal with these situations.

In this article, I will describe this interface, the helper classes, and the parts of the implementation of the MemoryStorageFolder and FileSystemStorageFolder classes. In the near future, I will update this article to include the NtfsFileStorageFolder and then the (drum roll...) ComCompoundDocumentStorageFolder classes (both of which are written, but not yet in a form for public consumption). If any enterprising reader wishes to create implementations for IsolatedStorage, ZIP archives, MHT files or other types of storage, please include links to the code in the message section.

Using the code

The Zip file has a number of files. This is because I have a fairly large base library from which it is not trivial to extract components, for example. Sorry about that :(

Also, you'll want to review my StreamMuxer article (that code is included in here) to better understand my MemoryStorageFolder class.

IStorageFolder

No point pussy footing around, here it is:

using System;
using System.Collections.Generic;
using System.IO;

namespace RevolutionaryStuff.JBT.Storage
{
    /// <summary>
    /// An interface that encapsulates
    /// file-system like folders and files
    /// </summary>
    public interface IStorageFolder : IDisposable
    {
        /// <summary>
        /// Get the names of the folders directly contained in this folder
        /// </summary>
        /// <returns>The name of the folders</returns>
        IEnumerable<string> GetFolderNames();
        /// <summary>
        /// Create a new child folder
        /// </summary>
        /// <remarks>Should fail if a stream
        /// of the same name already exists</remarks>
        /// <param name="name">The name of the folder</param>
        /// <returns>The folder</returns>
        IStorageFolder CreateFolder(string name);
        /// <summary>
        /// Open an existing child folder
        /// </summary>
        /// <param name="name">The name of the folder</param>
        /// <returns>The folder</returns>
        IStorageFolder OpenFolder(string name);

        /// <summary>
        /// Get the names of the streams directly contained in this folder
        /// </summary>
        /// <returns></returns>
        IEnumerable<string> GetStreamNames();
        /// <summary>
        /// Create a new stream / overwrite an existing stream
        /// </summary>
        /// <remarks>Should fail if a folder
        /// of the same name already exists</remarks>
        /// <param name="name">The name of the stream</param>
        /// <returns>The stream</returns>
        Stream CreateStream(string name);
        /// <summary>
        /// Open an existing child stream
        /// </summary>
        /// <param name="name">The name of the stream</param>
        /// <returns>The stream</returns>
        Stream OpenStream(string name);

        /// <summary>
        /// Delete either a child folder or child stream
        /// </summary>
        /// <remarks>
        /// This should return silently if the resource did not exist.
        /// This should fail if the folder has items
        /// </remarks>
        /// <param name="name">The name of the resource</param>
        void Delete(string name);

        /// <summary>
        /// Get the storage info for the given resource
        /// </summary>
        /// <param name="name">The name of the resource</param>
        /// <returns>The storage info</returns>
        IStorageInfo GetInfo(string name);
        /// <summary>
        /// Make a given name compatible with the current storage folder
        /// </summary>
        /// <param name="name">The current name</param>
        /// <returns>A name that is compatible with this storage.
        /// When possible, this will be the input name.</returns>
        string PurifyName(string name);
    }
}

You may look at this and ask why I have four different members for opening / creating child streams and folders? I thought about having the equivalent of File.Open(string path, FileMode mode, FileAccess access, FileShare share) and the equivalent for Directories, instead. After all, I've written a bunch of code that uses some off-the-wall combination of mode, access, and share. I decided against this because I wanted a simple interface that was easy to implement! If you need these operations, you may consider creating a second interface, IStorageFolder2, that contains this and other functions to help you along. I already created something like this (you'll see it in the source code), but did not include it here because I'm not sure exactly where I want to take it.

The PurifyName method exists because a name for a folder/stream in one IStorageFolder may be invalid in another. For instance, CompoundDocument limits a name to 31 characters, NTFS 254 or 255, and the memory one is unlimited. Calling this function will help you avoid pitfalls when a storage folder is switched from underneath you. Therefore, you should not encode valuable information inside of the file name itself, as that name may not be valid for your given folder.

IStorageInfo

This interface abstracts information about streams and folders contained inside of a storage folder. You can think of this as the equivalent of BCL FileInfo and DirectoryInfo classes. Like IStorageFolder, this interface is small to reduce the barriers to implement. If you wish to extend, you should think about creating an auxiliaryIStorageInfo2 interface.

using System;

namespace RevolutionaryStuff.JBT.Storage
{
    /// <summary>
    /// Information about the stream/folder of an IStorageFolder
    /// </summary>
    public interface IStorageInfo
    {
        /// <summary>
        /// The name of the item
        /// </summary>
        /// <remarks>Set MAY throw
        /// a NotSupportedException</remarks>
        string Name { get; set; }
        /// <summary>
        /// The size of the item. 0 if it is a folder
        /// </summary>
        long Size { get; }
        /// <summary>
        /// The time (int UTC) the item was created
        /// </summary>
        DateTime CreationTimeUtc { get; }
        /// <summary>
        /// When true, the item is a folder, else it is a stream.
        /// </summary>
        bool IsFolder { get; }
    }
}

StorageStuff

This static class contains a number of useful methods for dealing with IStorageFolder. The method signatures are shown below, with only the code for FindOrigName included (the others should be self explanatory). FindOrigName should be used when you want to create a new stream with a name, but if an existing stream has that name, alter the name so that it is unique.

using System;
using System.IO;
using RevolutionaryStuff.JBT.Streams;

namespace RevolutionaryStuff.JBT.Storage
{
    /// <summary>
    /// This contains helper functions for IStorageInfo
    /// </summary>
    public static class StorageStuff
    {
        /// <summary>
        /// Open an existing stream / create a new stream
        /// if one doesn't already exist
        /// </summary>
        /// <param name="folder">The folder</param>
        /// <param name="streamName">The stream name</param>
        /// <returns>The stream</returns>
        public static Stream OpenOrCreateStream(IStorageFolder folder, 
                                                string streamName)
        {...}

        /// <summary>
        /// Open an existing folder / create a new folder
        /// if one doesn't already exist
        /// </summary>
        /// <param name="folder">The folder</param>
        /// <param name="subFolderName">The folder 
        ///         to open/create</param>
        /// <returns>The folder</returns>
        public static IStorageFolder OpenOrCreateFolder(IStorageFolder 
                                          folder, string subFolderName)
        {...}

        /// <summary>
        /// Does the given folder contain a resource of the given name?
        /// </summary>
        /// <param name="folder">A folder</param>
        /// <param name="name">The name of a resource</param>
        /// <returns>True if a resource of this
        /// name is a direct child</returns>
        public static bool Contains(IStorageFolder folder, string name)
        {...}


        /// <summary>
        /// Find a new purified name for a new resource that
        /// is unique in the folder and similar to the given name
        /// </summary>
        /// <param name="folder">The folder</param>
        /// <param name="name">The proposed name</param>
        /// <returns>The uniqified name</returns>
        public static string FindOrigName(IStorageFolder folder, string name)
        {
            Validate.NonNullArg(folder, "folder");
            Validate.StringArg(name, "name");

            name = folder.PurifyName(name);
            if (CreateBlank(folder, name)) return name;
            string nameNoExt = Path.GetFileNameWithoutExtension(name);
            string ext = Path.GetExtension(name);
            for (int z=0;;++z)
            {
                string proposed = string.Format("{0} ({1}){2}", nameNoExt, z, ext);
                proposed = folder.PurifyName(proposed);
                if (CreateBlank(folder, proposed)) return proposed;
            }
        }

        /// <summary>
        /// Recursivly copy a source storage
        /// folder to a destination storage folder
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="dest">The dest</param>
        public static void Copy(IStorageFolder source, IStorageFolder dest)
        {...}

        /// <summary>
        /// Recursivly copy a source storage
        /// folder to a destination storage folder
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="dest">The dest</param>
        /// <param name="purifyNamesForDestFolder">
        /// When true, purify names for the destination folder,
        /// When false, throw an exception if the dest cannot accept the new name
        /// </param>
        public static void Copy(IStorageFolder source, IStorageFolder dest, 
               Predicate<IStorageInfo> predicate, 
               bool purifyNamesForDestFolder)
        {...}

        /// <summary>
        /// Delete the contents of the given folder
        /// </summary>
        /// <param name="folder">The folder</param>
        public static void DeleteContents(IStorageFolder folder)
        {...}
    }
}

Example Usage

Below (and of course in the zip) is a dumb example of how to use IStorageFolder.

using System;
using System.IO;
using RevolutionaryStuff.JBT;
using RevolutionaryStuff.JBT.Storage;

namespace StorageFolderExample
{
    class Program
    {
        private static void ConstructStreams(IStorageFolder folder, 
                string messageFormat, string baseStreamName)
        {
            Validate.NonNullArg(folder, "folder");

            for (int z = 0; z < 4; ++z)
            {
                string name = 
                  StorageStuff.FindOrigName(folder, baseStreamName);
                using (Stream st = 
                       StorageStuff.OpenOrCreateStream(folder, name))
                {
                    StreamWriter sw = new StreamWriter(st);
                    sw.WriteLine(messageFormat, folder.GetType(), z);
                    sw.Flush();
                }            
            }
        }

        private static void Construct(IStorageFolder folder)
        { 
            Validate.NonNullArg(folder, "folder");

            ConstructStreams(folder, "I am instance #{1}." + 
                             "  Parent is root!", "TestFile.txt");
            using (IStorageFolder f = 
                   StorageStuff.OpenOrCreateFolder(folder, "a"))
            {
                ConstructStreams(f, "I am instance #{1}." + 
                                 "  Parent is A!", "TestFile.txt");
            }
            using (IStorageFolder f = 
                   StorageStuff.OpenOrCreateFolder(folder, "b"))
            {
                ConstructStreams(f, "I am instance #{1}.  Parent is B!", 
                                 "TestFileWithReallyLongNameWithBADCHARS" + 
                                 " #/\\!@#$&() 012345678901234567890123456" + 
                                 "78901234567890123456789012345678901234" + 
                                 "567890123456789012345678901234567890123" + 
                                 "456789012345678901234567890123456789" + 
                                 "012345678901234567890123456789012345" + 
                                 "67890123456789012345678901234567" + 
                                 "890123456789_I_TOLD_U.txt");
            }
        }

        private static void Print(IStorageInfo i, string indent, string extra)
        {
            Console.WriteLine(string.Format(
                "{0}name:[{1}] size:{2} isFolder:{3}\n{0}extra: [{5}]",
                indent, i.Name, i.Size, i.IsFolder, 
                i.CreationTimeUtc.ToLocalTime(), extra));
        }

        private static void Print(IStorageFolder folder, string indent)
        {
            Validate.NonNullArg(folder, "folder");

            foreach (string name in folder.GetStreamNames())
            {
                IStorageInfo i = folder.GetInfo(name);
                using (Stream st = folder.OpenStream(name))
                {
                    StreamReader r = new StreamReader(st);
                    Print(i, indent, r.ReadToEnd());
                }
            }

            foreach (string name in folder.GetFolderNames())
            {
                IStorageInfo i = folder.GetInfo(name);
                Print(i, indent, "");
                using (IStorageFolder f = folder.OpenFolder(name))
                {
                    Print(f, indent+"\t");
                }
            }        
        }

        static void Main(string[] args)
        {
            IStorageFolder mf = new MemoryStorageFolder();
            Construct(mf);
            Console.WriteLine("Memory Storage Folder");
            Print(mf, "");
            IStorageFolder ff = new 
                FileSystemStorageFolder(@"c:\storagefoldertest");
            StorageStuff.Copy(mf, ff, null, true);
            Console.WriteLine("File Storage Folder");
            Print(ff, "");
        }
    }
}

This is my fourth code submission (almost a silver member!). If you like what I've done, vote! If you don't, post messages and ask questions. I have a bunch of code to contribute, but since it takes time, will only do so if people are getting value from it.

Happy coding Smile | :)

History

  • 2/24/2006 - First submission.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Jason.Thomas

United States United States
No Biography provided

Comments and Discussions

 
QuestionPlease help PinmemberSUNDARA GANESH27-Nov-11 17:44 
GeneralGood Stuff Pinmemberdsimunic28-Oct-06 7:06 
GeneralI need it! Pinmemberjkdlksfjafljdsl10-Jun-06 16:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411022.1 | Last Updated 27 Feb 2006
Article Copyright 2006 by Jason.Thomas
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid