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
{
public interface IStorageFolder : IDisposable
{
IEnumerable<string> GetFolderNames();
IStorageFolder CreateFolder(string name);
IStorageFolder OpenFolder(string name);
IEnumerable<string> GetStreamNames();
Stream CreateStream(string name);
Stream OpenStream(string name);
void Delete(string name);
IStorageInfo GetInfo(string name);
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
{
public interface IStorageInfo
{
string Name { get; set; }
long Size { get; }
DateTime CreationTimeUtc { get; }
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
{
public static class StorageStuff
{
public static Stream OpenOrCreateStream(IStorageFolder folder,
string streamName)
{...}
public static IStorageFolder OpenOrCreateFolder(IStorageFolder
folder, string subFolderName)
{...}
public static bool Contains(IStorageFolder folder, string name)
{...}
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;
}
}
public static void Copy(IStorageFolder source, IStorageFolder dest)
{...}
public static void Copy(IStorageFolder source, IStorageFolder dest,
Predicate<IStorageInfo> predicate,
bool purifyNamesForDestFolder)
{...}
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 :)
History
- 2/24/2006 - First submission.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.