Wednesday, April 05, 2006

It's been a long time since I've posted something "chewable". My energies were all directed on the soon to be released Xceed Zip for .NET version 3.0. Though the apparent changes on the public interface are quite minor, and one can teach the new classes quite easily, the underlying code wasn't trivial.

In short, for all of you who know what ZipArchive, ZippedFile and ZippedFolder are, say hello to TarArchive, TarredFile, TarredFolder, GZipArchive and GZippedFile.

And when I say "easy to teach", what it really means is "find yourself a zipping example, and replace class name occurances of Zip{something} with Tar{somethong} or GZip{something}".

Sure, there are some gotchas, like the fact that a GZIP archive cannot contain filenames with subfolders, are not well-suited to contain more than one compressed file, and can contain files without filenames. But these are details you'll get used to quite easily.

There are two things that make me really proud in that product. One is under the hood, and the other is a sample. First, the engine: My colleague Jacques and I have come up with what we call the "Storage Engine". It's an abstraction of what an archiving library needs in term of temp storage, in-place archive updating, and transactional operations on an archive. Both the new TAR and GZIP implementations use it. In short, it abstracts the fact that we want to always update an archive in-place when possible, but revert to temp files and make sure to commit those temp files with any existing archive upon the last modification of it. If things go well, the ZIP implementation will benefit from it sooner than later.

Second, the sample: The FTP Sample Explorer is gone, replaced with the FileSystem Snippet Explorer, a sample that let's you see, modify and run code snippets that show you the various tasks one might wish to implement. It goes straight to the point. No bells and whistle, no gravy, just the meat. The code is embedded in the executable as compressed serialized XML data. The main information (each topic's description and code) is nothing else than rich text. The nice thing about this sample is that in order for me to modify and add new topics, I simply need to compile the project with an extra define, and I'm now running the application in "admin" mode, enabling me to update the compressed XML file directly, for the next compilation to benefit from this update.

Though I've finished work on this 3.0 version, I already have both hands in the two next releases of Xceed Zip for .NET and Xceed FTP for .NET. The first one will add support for AES encryption, and the second one will now offer proxy support.

I didn't have much time to write because all those releases have a tight schedule I can't bust. I'm leaving Xceed in two months. Yup, I've decided it was time for me to move on. Until then, I have agreed to complete AES implementation, help Jacques kick-start proxy support and train about everybody here, each earning one of the numerous hats I'm wearing. It was a very difficult decision, since I have only friends at Xceed. Though the nine years or so I've spent here were exciting and challenging, I feel it's time for me to try new stuff... by myself. This isn't a divorce. I won't be far from Xceed, and still available to help them from time to time. As for you, dear customers and readers, rest assured you will stay in good hands. The team behind Xceed Zip and Xceed FTP, both .NET and ActiveX, will remain strong, even get stronger than it is now.


.NET | FTP | General | Zip

4/5/2006 8:43:13 AM (Eastern Daylight Time, UTC-04:00)  #   
 Tuesday, January 31, 2006

Ever since I've been working with the .NET framework, most of my time was spent on the System.IO namespace. I'm not a UI guy, I'm an IO guy! The most important class in that namespace is System.IO.Stream. And since it was well-designed, probably inspired by other successful stream implementations (Delphi comes to mind), it's very easy to expose features using streams.

My favorite use of streams is for pass-through streams. A pass-through stream is a class which derives from System.IO.Stream, but reads from or writes to an inner stream received at creation. It serves as a data modifyer or data analyser. When reading from a pass-through stream, it first reads from its inner stream, then processes the data read (potentially modifying it) and returns this data. When writing to a pass-through stream, it first processes the provided data (again potentially modifying it), then writes it to its inner stream.

Xceed Zip for .NET and Xceed FTP for .NET both use a pletoria of pass-through streams. The most popular is Xceed.Compression.CompressedStream, the stream responsible for compressing data before writing it to its inner stream, or decompressing data read from its inner stream. But most others are internal. We've been juggling with the idea of exposing them for a long time, but beleive it would only confuse developers to "see" those new namespaces and classes. Another useful thing with internal classes is that we can change their interface without causing breaking changes.

TransientStream

It was a long debate before we decided to go forth with the "transient" keyword. Not only is it used in the TransientStream type name, but also as a property on many of our pass-through streams. What we meant by "transient" is "volatile", or if you prefer more explicit keywords, "does-not-close-its-inner-stream-when-closed". A TransientStream is about the simplest expression of a pass-through stream. All required property and method overrides simply call the inner stream. The only exception is for the Close method, which simply makes sure not to call Close on the inner stream. This is very useful when you need to pass your stream to another routine which closes the stream, while you don't want your stream to get closed.

ChecksumStream

This stream does not modify the data read from or written to, but takes the opportunity to calculate either a CRC32 or an Adler32 on that data. When reading, it can also make sure, upon closing it, that the calculated checksum matches an expected stream, else throw an exception. In this way, we can insert checksum calculation anywhere in a process without interfering nor requiring code changes.

CombinedStream

The deflate compression algorithm has the ability to detect the end of the data when decompressing. The CompressedStream is itself a pass-through stream. When reading from it, it first reads from the inner stream, then decompresses the data. When it reaches the end of the compressed data, the CompressedStream has the ability to return a stream on the remaining data, in case this inner stream contains more data after the compressed block. Why isn't this equivalent to the inner stream you might ask? Let's say the inner stream isn't seekable. The CompressedStream's Read method first reads N bytes from the inner stream, but may have found that the end of the compressed data is after M bytes (M < N). The inner stream is already N-M bytes too far. The CombinedStream receives both a byte array (the unused N-M bytes) and the inner stream as ctor parameters, and will expose those as one contiguous stream. Pretty slick!

HeaderFooterStream

Xceed Streaming Compression for .NET exposes stream-based (as opposed to archive-based) compression formats. Those formats all have one thing in common: they have a header and a footer. Not all of them can depend on the deflate algorithm to automatically detect the end of the stream. That's why they need to make sure to never return the first M bytes and last N bytes from the inner stream, where M is the expected header size and N the expected footer size.

WindowStream

When exposing part of a zip file as a single AbstractFile, we need to make sure we do not read past the boundaries of that file's data in the zip file. The WindowStream exposes a region of its inner stream as a zero-position, N-length stream.

ZCryptStream

This pass-though stream automatically encrypts or decrypts the data written or read, using the basic Zip encryption (which is as weak as me in front of a cheese cake). I will be working on AES encryption very soon, and it will most probably be implemented as a pass-through stream too!

NotifyStream

Though pass-through streams can do much of the task, it is often better for the clarity of the code to have processing done by other classes not deriving from System.IO.Stream. The NotifyStream class exposes three events: ReadingFromStream, WritingToStream and ClosingStream. Any other class can advise for those events to intervene in the reading or writing process. This old class exists since the beginning of Xceed Zip for .NET, but it has proven very useful in the current development we are doing for Tar and GZip support within Xceed Zip for .NET.

ForwardSeekableStream

This new class created for Xceed Zip for .NET 3.0 (Tar and GZip support) can expose a non-seekable stream as a seekable stream when reading, or at least a stream reporting a Position when writing. When reading, you can call Seek with an offset behond the current position, and it will simply read from the non-seekable inner stream until well positioned. And for both reading and writing operations, it counts the number of bytes read or written so it can report a position (granting we knew the original position when created).

FtpAsciiDataStream

Xceed FTP for .NET also uses pass-through streams. For example, the FtpAsciiDataStream wraps the NetworkStream to perform convertion of LF to CR/LF on the fly when sending a file in ASCII mode.


.NET | Compression | FileSystem | FTP | Zip

1/31/2006 9:47:29 AM (Eastern Standard Time, UTC-05:00)  #   
 Tuesday, November 01, 2005

Just in case my previous post on the subject did not ring a bell, the release of version 2.1 of Xceed FTP for .NET means you can directly unzip from a zip file located on an FTP server, without downloading the file first! Look at the following code:

  using( FtpConnection connection = new FtpConnection( "ftp.xceed.com" ) )
  {
    FtpFile source = new FtpFile( connection, @"/images/Flowers/Backup/Flowers.zip" );
    DiskFolder dest = new DiskFolder( @"d:\temp\flowers" );

    ZipArchive zip = new ZipArchive( source );
    zip.CopyFilesTo( dest, true, true );
  }

The secret behind this code is the kind of stream "FtpFile.OpenRead" returns. Though we are dealing with a network connection, this stream is fully seekable! The FtpFile takes advantage of the "REST" FTP command, which tells the FTP server we wish to start the transfer at a specific offset. Thus, when the ZipArchive needs to seek at the end of the file to locate the ending header, a proper "REST" command is issued to avoid having to read all the zip file first. And the same happens when reading the central directory, or unzipping specific files.


.NET | FileSystem | FTP | Zip

11/1/2005 4:15:40 PM (Eastern Daylight Time, UTC-04:00)  #   
 Wednesday, October 05, 2005

I'm glad to announce that Xceed FTP for .NET 2.1 is now available for download. I've been working on this release for the past few months, and I'm very excited to finally see the FTP FileSystem come to life.

For those not familiar with the Xceed FileSystem, which comes with Xceed Zip for .NET, here is some code that sheds some light on what you can do with it. Consider these variables of the same base type:

// A file on disk
AbstractFile first = new DiskFile( @"d:\FileSystem.txt" );
 
// Another file on disk
AbstractFile second = new DiskFile( @"c:\temp\AnotherFileSystem.txt" );
 
// A file compressed in a zip file on disk
AbstractFile third = new ZippedFile( 
  new DiskFile( @"c:\temp\data.zip" ), "FileSystemInAZip.txt" );;
 
// A file in the isolated storage
AbstractFile fourth = new IsolatedFile( "Isolated.txt" );
 
// A file in memory (random name)
AbstractFile fifth = new MemoryFile();
 
// A file compressed in a zip file in memory
AbstractFile sixth = new ZippedFile( 
  new MemoryFile(), "VolatileFileSystem.txt" );

You can copy files around very easily:

// Copying the first file anywhere else is always the same!
first.CopyTo( second, true );
first.CopyTo( third, true );
first.CopyTo( fourth, true );
first.CopyTo( fifth, true );
first.CopyTo( sixth, true );

And accessing the contents of any file is always the same:

private void DisplayTextFile( AbstractFile file )
{
  Console.WriteLine( "Displaying the contents of {0}, which is a {1}.", 
    file.FullName,
    file.GetType().Name );
 
  using( StreamReader reader = new StreamReader( file.OpenRead() ) )
  {
    string line;
 
    while( ( line = reader.ReadLine() ) != null )
    {
      Console.WriteLine( line );
    }
  }
 
  Console.WriteLine();
}

// Displaying the contents of those files is always the same!
DisplayTextFile( first );
DisplayTextFile( second );
DisplayTextFile( third );
DisplayTextFile( fourth );
DisplayTextFile( fifth );
DisplayTextFile( sixth );

And why not finish this demonstration by deleting the files we just created.

// And finally, deleting files is the same!
second.Delete();
third.Delete();
fourth.Delete();
fifth.Delete();
sixth.Delete();

Any kind of file is an AbstractFile. Any kind of folder is an AbstractFolder. This way, a DiskFile, an IsolatedFile, a ZippedFile and a MemoryFile share a common set of properties and methods for accessing their metadata and reading/writing their actual data. And a DiskFolder, an IsolatedFolder, a ZippedFolder and a MemoryFolder share a common set of methods for discovering child items.

How does the FTP FileSystem come into play? Simply by offering the same abstraction over files and folders stored on an FTP server. We could simply add this code to the above sample, and everything works just as expected!

AbstractFile seventh = new FtpFile( 
  new FtpConnection( "localhost" ), @"\RemoteFileSystem.txt" );
 
first.CopyTo( seventh, true );
 
DisplayTextFile( seventh );
 
seventh.Delete();

Let's dive a little bit into this implementation of an AbstractFile and AbstractFolder. Each constructor requires an FtpConnection instance, which contains information on how to connect to the target FTP server. Though it looks like a simple information storage class, it does much more. Each time an FtpFile or an FtpFolder requires information, or an incoming or outgoing stream on a file's data, it asks the FtpConnection for an active command channel connection to the server. This way, a unique command channel is generally required for accessing many files on the server.

using( FtpConnection connection = new FtpConnection( "ftp.xceed.com" ) )
{
  connection.TraceWriter = Console.Out;
 
  FtpFolder root = new FtpFolder( connection );
 
  foreach( AbstractFile file in root.GetFiles( false, "*.txt" ) )
  {
    DisplayTextFile( file );
  }
}

If we comment out the "Console.WriteLine( line );" line in "DisplayTextFile", we can see the FTP conversation that occurred for the above code:

Connected to 66.46.177.250:21 on 9/27/2005 @ 2:24:54 PM
< 220 Serv-U FTP Server v6.0 for WinSock ready...
> USER anonymous
< 331 User name okay, please send complete E-mail address as password.
> PASS *****
< 230 User logged in, proceed.
> PWD
< 257 "/" is current directory.
> CWD /
< 250 Directory changed to /
> CWD /
< 250 Directory changed to /
> CWD /
< 250 Directory changed to /
> TYPE A
< 200 Type set to A.
> PASV
< 227 Entering Passive Mode (66,46,177,250,6,238)
> LIST
Data connection established with 66.46.177.250:1774 on 9/27/2005 @ 2:24:56 PM
< 150 Opening ASCII mode data connection for /bin/ls.
< 226-Maximum disk quota limited to Unlimited kBytes
< Used disk quota 0 kBytes, available Unlimited kBytes
< 226 Transfer complete.
Displaying the contents of \FileSystem.txt, which is a FtpFile.
> CWD /
< 250 Directory changed to /
> TYPE I
< 200 Type set to I.
> PASV
< 227 Entering Passive Mode (66,46,177,250,6,240)
> RETR FileSystem.txt
Data connection established with 66.46.177.250:1776 on 9/27/2005 @ 2:24:57 PM
< 150 Opening BINARY mode data connection for FileSystem.txt (1198 Bytes).
< 226-Maximum disk quota limited to Unlimited kBytes
< Used disk quota 0 kBytes, available Unlimited kBytes
< 226 Transfer complete.

Displaying the contents of \appnote.txt, which is a FtpFile.
> CWD /
< 250 Directory changed to /
> TYPE I
< 200 Type set to I.
> PASV
< 227 Entering Passive Mode (66,46,177,250,6,246)
> RETR appnote.txt
Data connection established with 66.46.177.250:1782 on 9/27/2005 @ 2:24:58 PM
< 150 Opening BINARY mode data connection for appnote.txt (109785 Bytes).
< 226-Maximum disk quota limited to Unlimited kBytes
< Used disk quota 0 kBytes, available Unlimited kBytes
< 226 Transfer complete.

> QUIT
Disconnected from 66.46.177.250:21 on 9/27/2005 @ 2:25:04 PM

Each FtpFolder and FtpFile instance shared the same FtpConnection, and since no two operations were done at the same time, a single connection was required, as the log indicates. The FtpConnection object implements the IDisposable interface, since it keeps any active connection available until disposed (or finalized).

Now what happens if I try to open two files at the same time, like this?

using( FtpConnection connection = new FtpConnection( "ftp.xceed.com" ) )
{
  connection.TraceWriter = Console.Out;
 
  AbstractFile first = new FtpFile( connection, @"\FileSystem.txt" );
  AbstractFile second = new FtpFile( connection, @"\appnote.txt" );
 
  using( Stream firstStream = first.OpenRead() )
  {
    using( Stream secondStream = second.OpenRead() )
    {
      // In an FTP conversation with an FTP server, only one command
      // at a time can be pending. Here, we clearly have two files
      // open at the same time on the same FTP server. How? Each file
      // has its own connection to the FTP server!
    }
  }
}

The FtpConnection object will create extra command channel connections as required. The output shows two command channel connections were made:

Connected to 66.46.177.250:21 on 9/27/2005 @ 2:38:06 PM
< 220 Serv-U FTP Server v6.0 for WinSock ready...
> USER anonymous
< 331 User name okay, please send complete E-mail address as password.
> PASS *****
< 230 User logged in, proceed.
> CWD /
< 250 Directory changed to /
> TYPE A
< 200 Type set to A.
> PASV
< 227 Entering Passive Mode (66,46,177,250,10,53)
> LIST
Data connection established with 66.46.177.250:2613 on 9/27/2005 @ 2:38:07 PM
< 150 Opening ASCII mode data connection for /bin/ls.
< 226-Maximum disk quota limited to Unlimited kBytes
<     Used disk quota 0 kBytes, available Unlimited kBytes
< 226 Transfer complete.
> CWD /
< 250 Directory changed to /
> TYPE I
< 200 Type set to I.
> PASV
< 227 Entering Passive Mode (66,46,177,250,10,54)
> RETR FileSystem.txt
Data connection established with 66.46.177.250:2614 on 9/27/2005 @ 2:38:08 PM
< 150 Opening BINARY mode data connection for FileSystem.txt (1198 Bytes).
Connected to 66.46.177.250:21 on 9/27/2005 @ 2:38:08 PM
< 220 Serv-U FTP Server v6.0 for WinSock ready...
> USER anonymous
< 226-Maximum disk quota limited to Unlimited kBytes
<     Used disk quota 0 kBytes, available Unlimited kBytes
< 226 Transfer complete.
< 331 User name okay, please send complete E-mail address as password.
> PASS *****
< 230 User logged in, proceed.
> CWD /
< 250 Directory changed to /
> TYPE I
< 200 Type set to I.
> PASV
< 227 Entering Passive Mode (66,46,177,250,10,55)
> RETR appnote.txt
Data connection established with 66.46.177.250:2615 on 9/27/2005 @ 2:38:09 PM
< 150 Opening BINARY mode data connection for appnote.txt (109785 Bytes).
< 426-Maximum disk quota limited to Unlimited kBytes
<     Used disk quota 0 kBytes, available Unlimited kBytes
< 426 Data connection closed, file transfer appnote.txt aborted.
> QUIT
Disconnected from 66.46.177.250:21 on 9/27/2005 @ 2:38:13 PM
> QUIT
Disconnected from 66.46.177.250:21 on 9/27/2005 @ 2:38:13 PM

And the great part about all this is that you don't have to worry about this while coding. You're just manipulating yet another kind of AbstractFile or AbstractFolder.

If we get back to the Zip implementation of the Xceed FileSystem, you can see that a ZippedFile or ZippedFolder (or ZipArchive, the root ZippedFolder) constructor needs to know which AbstractFile is holding the actual zip file that should contain this file or folder. "AbstractFile" truly means "any file", as long as there is an AbstractFile-derived class somewhere to expose this file. It means that zipping directly onto an FTP server is no more difficult than zipping in a regular file on disk.

AbstractFolder source = new DiskFolder( @"d:\Data" );
 
AbstractFolder localDest = new ZipArchive( 
  new DiskFile( @"d:\temp\local.zip" ) );
 
AbstractFolder remoteDest = new ZipArchive(
  new FtpFile( new FtpConnection( "localhost" ), @"remote.zip" ) );
 
// Copying is the same, no matter what is the destination
// file or folder.
source.CopyTo( localDest, true );
source.CopyTo( remoteDest, true );

Code for zipping in "D:\temp\local.zip" is no different than code for zipping in "ftp://localhost/remote.zip". And obviously, reading or unzipping from any zip file is the same.

AbstractFolder localSource = new ZipArchive( 
  new DiskFile( @"d:\temp\local.zip" ) );
 
AbstractFolder remoteSource = new ZipArchive(
  new FtpFile( new FtpConnection( "localhost" ), @"remote.zip" ) );
 
AbstractFolder dest = new DiskFolder( @"d:\restored" );
 
// Unzipping text files from any source is the same!
localSource.CopyFilesTo( dest, true, true, "*.txt" );
remoteSource.CopyFilesTo( dest, true, true, "*.txt" );

I really hope this new addition to the Xceed FileSystem will generate the same enthusiasm we had inventing and developping it. I'm very interested in hearing your opinions!


.NET | FileSystem | FTP | Zip

10/5/2005 3:09:39 PM (Eastern Daylight Time, UTC-04:00)  #   
 Thursday, July 14, 2005

I'm currently working hard on the FileSystem support for Xceed FTP for .NET, and I had to share with you a screenshot of one of my tests. My XceedBox.NET sample is now fully supporting FtpFile and FtpFolder mapping.

XceedBoxNET.png

As you can see, my working folder is a zip file located in a folder on an FTP server. The "type" command above has no idea what kind of file it is displaying. It receives an AbstractFile, gets a Stream on it via OpenRead, reads and displays the contents. That AbstractFile is actually a ZippedFile found in a ZippedFolder constructed around an FtpFile found in an FtpFolder.


.NET | FileSystem | FTP

7/14/2005 4:04:21 PM (Eastern Daylight Time, UTC-04:00)  #   
 Thursday, June 09, 2005

In the new package v1.2.5309 which will be available for download next week resides a new feature you won't see much emphasis about, but which I was very eager to complete. You can now create a ZipArchive instance around an AbstractFile that does not support reading from.

(drum roll) ... (looking around) ... Nobody's applauding? That's because you probably don't know yet how useful this can be.

Most ASP.NET applications that wish to create zip files on the fly and send them in the response are either stuck with creating those zip files on disk in a temporary filename, or create them in a MemoryFile, then copy that MemoryFile in the response stream.

However, the StreamFile class was created for such purposes of exposing any existing Stream as an AbstractFile. You already could create a StreamFile around the Response's OutputStream. But passing that StreamFile to the ZipArchive's constructor would fail, because it can't read from it. Instead of assuming an empty zip file, it miserably failed. Shame.

No more... Since version 2.2.5302, it will assume the zip file is empty. So code like this works perfectly:

    public void ProcessRequest(HttpContext context)
    {
      context.Response.ContentType = "application/zip";
      context.Response.AddHeader( "Content-Disposition", "attachment; filename=images.bmp" );
 
      ZipArchive archive = new ZipArchive( new StreamFile( context.Response.OutputStream ) );
      DiskFolder source = new DiskFolder( context.Request.MapPath( "." ) );
 
      source.CopyFilesTo( archive, false, false, "*.bmp" );
    }

The same problem appeared when trying to combine Xceed Zip for .NET with Xceed FTP for .NET, to upload zip files directly on the FTP server. Though the FtpClient class exposes a very useful GetUploadStream method to get a direct stream on the data connection, code like this previously failed.

          using( Stream upload = client.GetUploadStream( "images.zip" ) )
          {
            ZipArchive archive = new ZipArchive( new StreamFile( upload ) );
            DiskFolder source = new DiskFolder( @"d:\images\" );
 
            source.CopyFilesTo( archive, false, false, "*.bmp" );
          }

Talk about short and sweet uploads of zip files!


.NET | FileSystem | FTP | Zip

6/9/2005 2:56:43 PM (Eastern Daylight Time, UTC-04:00)  #   
 Friday, February 18, 2005

A new Mono release today! As usual, I uninstall the previous release, install the new one, and test a few Xceed Zip for .NET console samples. Works perfectly!

But today I decided to try my Xceed FileSystem-based Command Prompt under Mono. Wow! Everything works almost perfectly. Zip, Ftp, Ram drive. Only a glitch with the Isolated Storage. This sample uses Mentalis.org's ConsoleAttributes library to customize the look and feel of the console. It works perfectly well with Mono (on Windows).

Any Mono users out there using Xceed products?


FileSystem | FTP | Zip | Mono

2/18/2005 10:33:14 AM (Eastern Standard Time, UTC-05:00)  #   
 Wednesday, February 02, 2005

I've been working part time (translation: I should be working on something else) on a new sample: my own Command Prompt. I know, I'm reinventing the wheel, not to count that Microsoft will launch a new one called msh (codename Monad). But it was more a concept or proof around exposing AbstractFolder and AbstractFile within a command prompt.

E:\>dir

  Directory of E:\

      DATE     TIME     SIZE or TYPE NAME
27/12/2004  4:23 PM         [FOLDER] Backup
03/11/2004 10:33 AM         [FOLDER] Chart30
24/11/2004 10:09 AM         [FOLDER] CLR Profiler
11/01/2005  5:24 PM         [FOLDER] Config.Msi
24/11/2004 10:10 AM         [FOLDER] My Music
12/01/2005  4:13 PM         [FOLDER] My Pictures
10/09/2004  1:52 PM         [FOLDER] RECYCLER
30/09/2004  8:40 PM         [FOLDER] System Volume Information
02/02/2005  2:49 PM         [FOLDER] temp
03/11/2004 10:33 AM         [FOLDER] XceedProjectsNET
25/01/2005  7:06 AM              143 toto.txt

  Files: 1  Folders: 14  Total file size: 143

E:\>copy toto.txt temp
 100%
E:\>cd temp
E:\temp\>

As you can see, I can list the contents of folders, copy files, and change the working folder. The application simply manages a working "AbstractFolder", and enables commands to act on that folder (or an AbstractFolder obtained from an absolute path).

The sample quicky evolved into a prototype for upcoming features. Among other things, I needed a way to recognize a path like "E:\temp\test.zip\images" as a ZippedFolder within a zip file. Let's stop the talking, and show some traces:

E:\temp\>md test.zip
E:\temp\>md test.zip\images
E:\temp\>copy "..\My Pictures\Chalet\*" test.zip\images
 100%
E:\temp\>

What have I done here? Create a folder named "test.zip"? Well, the "md" command recognized the ".zip" extension as a request to create a new empty zip file. The second "md" command actually created a new folder within the zip file. And the paths can freely use the zip filename as a folder part for any command, as shown with the copy example. If we display the contents of "E:\temp", we see the two expected files:

E:\temp\>dir

  Directory of E:\temp\

      DATE     TIME     SIZE or TYPE NAME
02/02/2005  3:00 PM         40068736 test.zip
25/01/2005  7:06 AM              143 toto.txt

  Files: 2  Folders: 0  Total file size: 40068879

E:\temp\>

As you can see, "test.zip" is really a file (DiskFile) within "E:\temp" (DiskFolder). What happens if I try changing the current folder into that zip file?

E:\temp\>cd test.zip
E:\temp\test.zip\>dir

  Directory of E:\temp\test.zip\

      DATE     TIME     SIZE or TYPE NAME
02/02/2005  3:00 PM         [FOLDER] images

  Files: 0  Folders: 1  Total file size: 0

E:\temp\test.zip\>cd images
E:\temp\test.zip\images\>dir

  Directory of E:\temp\test.zip\images\

      DATE     TIME     SIZE or TYPE NAME
06/08/2000  4:40 PM          6400006 Chaises.bmp
06/08/2000  4:35 PM          6348550 Chute.bmp
06/08/2000  4:29 PM          6337678 Ciel1.bmp
06/08/2000  4:30 PM          6396226 Ciel2.bmp
06/08/2000  4:33 PM          6414418 Ciel3.bmp
06/08/2000  4:38 PM          6524278 Couple.bmp
06/08/2000  4:37 PM          6388054 Martine.bmp
06/08/2000  4:32 PM          6405478 Ombre.bmp
06/08/2000  4:41 PM          6359254 Rochers.bmp

  Files: 9  Folders: 0  Total file size: 57573942

E:\temp\test.zip\images\>

The zip file is exposed as a folder, because the path "E:\temp\test.zip" was recognized and mapped to a ZippedFolder around a DiskFile. And "images" is nothing more than a subfolder within that root ZippedFolder, actually something like:

new ZippedFolder( new DiskFile( @"E:\temp\test.zip" ), @"\images" );

Ok, let's get into serious things:

E:\temp\test.zip\images\>cd ..\..
E:\temp\>copy *.zip RAM:\
 100%
E:\temp\>cd RAM:\test.zip\images
RAM:\test.zip\images\>dir m*.bmp

  Directory of RAM:\test.zip\images\

      DATE     TIME     SIZE or TYPE NAME
06/08/2000  4:37 PM          6388054 Martine.bmp

  Files: 1  Folders: 0  Total file size: 6388054

RAM:\test.zip\images\>

My Command Prompt exposes a root MemoryFolder called "RAM:\", which I can freely use. The commands act the same, no matter if I'm deeling with a ZippedFolder around a DiskFile or a MemoryFile. Want more?

RAM:\test.zip\images\>cd ftp://vermouth
ftp://vermouth\>dir

  Directory of ftp://vermouth\

      DATE     TIME     SIZE or TYPE NAME

  Files: 0  Folders: 0  Total file size: 0

ftp://vermouth\>md foobar.zip
ftp://vermouth\>copy "E:\My Music\WMA\Mes Aieux" foobar.zip
 100%
ftp://vermouth\>dir

  Directory of ftp://vermouth\

      DATE     TIME     SIZE or TYPE NAME
02/02/2005  3:20 PM         62936759 foobar.zip

  Files: 1  Folders: 0  Total file size: 62936759

ftp://vermouth\>dir c:\inetpub\ftproot

  Directory of c:\inetpub\ftproot\

      DATE     TIME     SIZE or TYPE NAME
02/02/2005  3:20 PM         62936759 foobar.zip

  Files: 1  Folders: 0  Total file size: 62936759

ftp://vermouth\>

FTP servers are threated as any other kind of AbstractFolder. The application simply recognize the "FTP:" prefix as a signature for a root FtpFolder, as it did with "RAM:" exposed as a MemoryFolder. The command implementations don't care what kind of AbstractFolder or AbstractFile they are dealing with.

The engine behind this involves FileSystemMapper-derived classes. They mainly get asked two kinds of questions:

Question 1: Do you recognize this path as a root?

If so, they remove the part of the path they could recognize as a root folder, and return the matching AbstractFolder.

Examples of mappers and their responsability:

  • DiskMapper : Drive letters and UNC paths (yes, you can "cd" into a UNC path!)
  • FtpMapper : The "FTP:" prefix with server name, and optional username and password (e.g. ftp://user:pass@vermouth:9999)
  • IsolatedStorageMapper : A custom prefix name like "STORE:" (that's the one my sample app supports).
  • MemoryMapper : A custom prefix used to create the initial root MemoryFolder, like "RAM:" (that's the one my app supports). You can create more than one MemoryMapper to have more than one ram drive.

Question 2: Can you represent this AbstractFile as an AbstractFolder?

If so, they simply return the matching AbstractFolder.

An example of such a mapper:

  • ZipFileMapper : It simply checks if the provided AbstractFile exists, then tries to create a ZipArchive around that AbstractFile in a try/catch. If it succeeds, it returns this ZipArchive (which derives from ZippedFolder).

Curiously, today I came across a post on our forums asking how to detect if a file is really a zip file. I gave this man the "new ZipArchive within a try/catch" solution, and he came back, as I feared, with concerns with the time wasted catching an exception for all those non-zip files. It's actually one of the bottlenecks of my Command Prompt sample. A lot of time is wasted throwing an exception for all non-zip files my app comes across. Well, I guess I'll have to work sooner than later on a new "ZipArchive.IsZipFile" method! :-)

Now, you have to convince my boss I should put more time on this sample and these new FileSystem features! Does mapping absolute paths like shown above to their proper AbstractFolder or AbstractFile something that could be usefull for you?


FileSystem | FTP | Samples | Zip

2/2/2005 3:55:51 PM (Eastern Standard Time, UTC-05:00)  #   
 Tuesday, January 18, 2005

I'm currently doing some tests on an alpha version of Xceed FTP for .NET, supporting the Xceed FileSystem. It's so wonderful to be able to manipulate files and folders no mather where they reside. Take this generic directory listing method:

private static void DisplayListing( AbstractFolder folder )
{
  if( !folder.Exists )
  {
    Console.WriteLine( "\n  Folder {0} does not exist.\n", folder.FullName );
  }
  else
  {
    FileSystemItem[] items = folder.GetItems( false );
    long totalSize = 0;
    int fileCount = 0;
 
    Console.WriteLine( "\n  Folder listing of {0}\n", folder.FullName );
 
    foreach( FileSystemItem item in items )
    {
      Console.Write( "{0} {1} ", 
        item.LastWriteDateTime.ToShortDateString(),
        item.LastWriteDateTime.ToShortTimeString() );
 
      AbstractFile file = item as AbstractFile;
 
      if( file == null )
      {
        Console.Write( "            " );
      }
      else
      {
        Console.Write( "{0,16} ", file.Size.ToString( "N0" ) );
        totalSize += file.Size;
        ++fileCount;
      }
 
      Console.WriteLine( item.Name );
    }
 
    int folderCount = items.Length - fileCount;
 
    Console.WriteLine( "\n  {0} file{1}, {2} folder{3}, {4} bytes\n",
      fileCount.ToString(),
      ( fileCount == 1 ) ? string.Empty : "s",
      folderCount.ToString(),
      ( folderCount == 1 ) ? string.Empty : "s",
      totalSize.ToString() );
  }
}

As you can see, the code does not need to know what exactly is that AbstractFolder. People familiar with Xceed Zip for .NET already know we could call the above method like this:

DiskFolder folder = new DiskFolder( @"C:\Program Files\Microsoft SDKs\WinFX" );
DisplayListing( folder );

And obtain this kind of output:


  Folder listing of C:\Program Files\Microsoft SDKs\WinFX\
 24/11/2004   9:29 AM   <DIR>          bin
 24/11/2004   9:27 AM   <DIR>          Help
 24/11/2004   9:27 AM   <DIR>          License
 24/11/2004   9:27 AM   <DIR>          misc
 24/11/2004   9:29 AM   <DIR>          Setup
 24/11/2004   9:29 AM   <DIR>          VS Install Directory
 11/11/2004   6:03 PM           16,001 ReleaseNotes.htm
 12/01/2005   2:58 PM           17,297 SetEnv.cmd
  2 files, 6 folders, 33298 bytes

Or call the same method like this:

ZippedFolder folder = new ZippedFolder( 
  new DiskFile( @"D:\sample.zip" ), "ContMenuExt" );
DisplayListing( folder );

To get this output:


  Folder listing of \ContMenuExt\
 08/01/2001  11:29 AM            8,091 ContextMenu.cpp
 02/01/2001   4:15 PM            1,005 ContextMenu.h
 08/01/2001  11:32 AM            7,820 ContextMenuExt.cpp
 28/11/2000  11:23 AM              225 ContextMenuExt.def
 02/01/2001   5:02 PM            5,301 ContextMenuExt.dsp
 28/11/2000  11:23 AM              551 ContextMenuExt.dsw
 28/11/2000  11:23 AM              742 ContextMenuExt.h
 02/01/2001   4:12 PM            1,440 ContextMenuExt.rc
 08/01/2001   3:29 PM                2 ReadMe.txt
 02/01/2001   4:12 PM            1,195 resource.h
  10 files, 0 folders, 26372 bytes

With the upcoming version of Xceed FTP for .NET, it won't be more difficult to display the contents of a folder located on an FTP server:

FtpConnectionInfo info = new FtpConnectionInfo( "ftp.microsoft.com" );
FtpFolder folder = new FtpFolder( info );
DisplayListing( folder );

  Folder listing of \
 25/11/2002  12:00 AM   <DIR>          bussys
 21/05/2001  12:00 AM   <DIR>          deskapps
 20/04/2001  12:00 AM   <DIR>          developr
 18/11/2002  12:00 AM   <DIR>          KBHelp
 02/07/2002  12:00 AM   <DIR>          MISC
 16/12/2002  12:00 AM   <DIR>          MISC1
 25/02/2000  12:00 AM   <DIR>          peropsys
 02/01/2001  12:00 AM   <DIR>          Products
 04/04/2003  12:00 AM   <DIR>          PSS
 21/09/2000  12:00 AM   <DIR>          ResKit
 25/02/2000  12:00 AM   <DIR>          Services
 25/02/2000  12:00 AM   <DIR>          Softlib
  0 files, 12 folders, 0 bytes

As a mather of fact, stuff like this already works fine on my machine:

FtpConnectionInfo info = new FtpConnectionInfo( "ftp.cam.org", "***", "***" );
FtpFile ftpFile = new FtpFile( info, @"\pub\Photos.zip" );
 
if( ftpFile.Exists )
  ftpFile.Delete();
 
DiskFolder sourceFolder = new DiskFolder( @"E:\My Pictures\Clément\Petites" );
ZipArchive destFolder = new ZipArchive( ftpFile );
 
sourceFolder.CopyFilesTo( destFolder, true, true );
 
DisplayListing( destFolder );
DisplayListing( ftpFile.ParentFolder );

This is the output:


  Folder listing of \
 16/09/2004   9:39 AM           11,820 Buzz.jpg
 16/09/2004   9:40 AM           11,143 Chalet - Bercé.jpg
 16/09/2004   9:42 AM           15,749 Clément et Michel.jpg
 16/09/2004   9:43 AM           18,473 Clément et Papa.jpg
 16/09/2004   9:42 AM           15,004 Clément et Valérie.jpg
 14/02/2003   4:17 PM            7,984 clément1.jpg
 14/02/2003   4:18 PM           11,288 clément2.jpg
 14/02/2003   4:18 PM           10,648 clément3.jpg
 11/08/2004   3:25 PM           18,499 Famille.jpg
 11/08/2004   3:23 PM           36,734 Fier.jpg
 16/09/2004   9:40 AM           14,959 Grande discussion.jpg
 16/09/2004   9:41 AM           13,426 Maman et Clément.jpg
 11/08/2004   3:25 PM           24,594 Piscine.jpg
  13 files, 0 folders, 210321 bytes

  Folder listing of \pub\
 27/12/2004  11:06 AM            7,986 Builds du 2004-09-27.htm
 12/01/2005   4:13 PM          208,439 Clement.zip
 27/12/2004  11:06 AM            1,068 FileSystem.txt
 18/01/2005   2:44 PM          208,423 Photos.zip
 27/12/2004  11:06 AM   <DIR>          Second
 27/12/2004  11:06 AM           11,723 VSSWarning.jpg
 27/12/2004  11:06 AM          117,695 appnote.txt
 2