|
|
|
|
Open window on development at Xceed
|
|
|
 Tuesday, June 28, 2005
Google Earth (aka Keyhole 3) is out, and free! Go check it out!
|
|
 Thursday, June 16, 2005
Customer requests come in waves, as if fashion was driving the development industry. Lately, many customers were trying to compress log files. I've deviced it was time for a little sample.
The idea was to encode each string message in unicode and compress it in a plain file, one after the other. I could have used a zip file with each file entry representing a message, but for small messages, the zip headers would take too much space for nothing, wasting the need for compression in the first place.
The deflate compression method has one nice feature: it can detect the end of the compressed data while decompressing, without knowing the total compressed size. That's why the CompressedStream class exposes a GetRemainingStream method for retrieving a Stream reference on the rest of the data in the inner stream.
I've kept the sample real simple, so you get the general idea:
using System; using System.IO; using Xceed.Compression; namespace CompressedLogExample { public class CompressedLog { public CompressedLog( string filename ) { if( filename == null ) throw new ArgumentNullException( "filename" ); if( filename.Length == 0 ) throw new ArgumentException( "The filename cannot be empty.", "filename" ); Xceed.Compression.Licenser.LicenseKey = "SAMPLE-APPLICATION-KEY"; m_filename = filename; } public void AddMessage( string message ) { if( message == null ) throw new ArgumentNullException( "message" ); lock( m_lock ) { using( Stream fileStream = new FileStream( m_filename, FileMode.Append ) ) { using( CompressedStream compStream = new CompressedStream( fileStream ) ) { byte[] encodedMessage = System.Text.Encoding.Unicode.GetBytes( DateTime.Now.ToString() + Environment.NewLine + message ); compStream.Write( encodedMessage, 0, encodedMessage.Length ); } } } } public void DisplayMessages( TextWriter writer ) { if( writer == null ) throw new ArgumentNullException( "writer" ); lock( m_lock ) { try { using( Stream originalStream = new FileStream( m_filename, FileMode.Open ) ) { Stream workStream = originalStream; do { using( CompressedStream compStream = new CompressedStream( workStream ) ) { // We don't want compStream to close sourceStream! compStream.Transient = true; using( StreamReader reader = new StreamReader( compStream, System.Text.Encoding.Unicode ) ) { writer.WriteLine( reader.ReadToEnd() ); writer.WriteLine( "---" ); // Before closing the reader (thus compStream), acquire a stream on // the rest of the data if present. workStream = compStream.GetRemainingStream(); // We must do this AFTER calling GetRemainingStream since compStream // may have read more from its inner stream than necessary. if( workStream.Position == workStream.Length ) { workStream = null; } } } } while( workStream != null ); } } catch( FileNotFoundException ) { writer.WriteLine( "The log is empty." ); } } } private string m_filename = string.Empty; private object m_lock = new object(); } }
Unfortunately, you can't replace the CompressedStream with a formatted stream like GZipCompressedStream, because they do not expose a GetRemainingStream method yet. Too bad, since the GZipCompressedStream can store a few minimal informations in its header. I'll have to open a feature request about that!
Here is some sample code for using this CompressedLog class:
static void Main(string[] args) { CompressedLog log = new CompressedLog( @"d:\temp\log.cmp" ); while( true ) { Console.WriteLine( "Write your next message below (empty message to quit):" ); string line = Console.ReadLine(); if( line.Length == 0 ) break; log.AddMessage( line ); } Console.WriteLine(); Console.WriteLine( "Your messages were:" ); log.DisplayMessages( Console.Out ); Console.WriteLine( "Press <Enter> to quit." ); Console.ReadLine(); }
|
|
 Monday, June 13, 2005
 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!
|
|
 Thursday, May 19, 2005
Lately, people have been asking us how to abort a zipping operation with Xceed Zip for .NET. The official answer is "you can't", as there is no method or property exposed for this task, as opposed to Xceed Zip ActiveX with its simple Abort property. But the truth is you can, with relatively little coding.
Before we get into how to abort, let's talk a little bit about the ZipArchive's TempFolder property. By default, it points to the same folder as the static ZipArchive.DefaultTempFolder property, which itself points to the user's temp folder, as exposed by System.IO.Path.GetTempPath().
Though the library is designed to delete any file it creates in the temporary folder, this can occur only when instances get finalized if the operation failed in the middle of the process.
A good coding pattern I like to use is the following: ZipArchive zip = new ZipArchive( new DiskFile( @"d:\temp\backup.zip" ) ); zip.TempFolder = zip.TempFolder.CreateFolder( Guid.NewGuid().ToString() ); try { using( AutoBatchUpdate auto = new AutoBatchUpdate( zip ) ) { DiskFolder source = new DiskFolder( @"d:\Data" ); source.CopyTo( zip, true ); } } finally { zip.TempFolder.Delete(); }
This makes sure no temp file survive a zipping cycle. And with that pattern, I can set the "default" temporary location once using the static DefaultTempFolder property, and each instance will use a unique folder within this starting point.
Now that my zipping operations are cleaning their traces, we're ready to talk about aborting. Some key concepts:
-
The library isn't pumping messages, and does not offer async operations. If you want your WinForms application's "Abort" button to react, you will have to pump messages yourself somewhere.
-
There are three major operations behind the creation or modification of a zip file:
-
Compressing each new file.
-
Moving each file to keep from the original zip file (if updating an existing zip file).
-
Building the target zip file by appending data created by the above two steps.
-
Zip and FileSystem events get raised at many levels, so you should pass your ZipEvents instance everywhere an overload accepting a FileSystemEvents or ZipEvents instance exists.
Your "Abort" button (or any abort input you like) will simply raise a flag. It can't do more.
private bool m_abort = false; private void AbortButton_Click(object sender, System.EventArgs e) { m_abort = true; }
Then you handle three events matching the forementioned three steps, pump messages to keep a responsive application, and check if the flag is raised. You can safely use the same method for handling the three events.
private void CheckAbort_ByteProgression(object sender, ByteProgressionEventArgs e) { if( m_abort ) throw new ApplicationException( "The user aborted the operation." ); Application.DoEvents(); }
As you can see, if the flag is raised, I'm throwing an ApplicationException. This will result in a System.Reflection.TargetInvocationException being thrown by the originally called method. To get a well-behaved application, you obviously want to trap any exception the FileSystem could throw. You can catch any TargetInvocationException to display an "operation aborted" message. Here's my code for the full operation:
private void StartButton_Click(object sender, System.EventArgs e) { m_abort = false; StartButton.Enabled = false; AbortButton.Enabled = true; try { ZipEvents events = new ZipEvents(); // Advise for the three main events for checking abort flag. events.ByteProgression += new ByteProgressionEventHandler( CheckAbort_ByteProgression ); events.GatheringZipContentByteProgression += new GatheringZipContentByteProgressionEventHandler( CheckAbort_ByteProgression ); events.BuildingZipByteProgression += new BuildingZipByteProgressionEventHandler( CheckAbort_ByteProgression ); // What's cool with delegates is that you can separate logic from UI. events.ByteProgression += new ByteProgressionEventHandler( UpdateUI_ByteProgression ); ZipArchive zip = new ZipArchive( events, null, new DiskFile( @"d:\temp\backup.zip" ) ); zip.TempFolder = zip.TempFolder.CreateFolder( Guid.NewGuid().ToString() ); try { using( AutoBatchUpdate auto = new AutoBatchUpdate( zip, events, null ) ) { DiskFolder source = new DiskFolder( @"d:\Data" ); source.CopyTo( events, null, zip, true ); } } finally { zip.TempFolder.Delete(); // Clean up events. events.ByteProgression -= new ByteProgressionEventHandler( CheckAbort_ByteProgression ); events.GatheringZipContentByteProgression -= new GatheringZipContentByteProgressionEventHandler( CheckAbort_ByteProgression ); events.BuildingZipByteProgression -= new BuildingZipByteProgressionEventHandler( CheckAbort_ByteProgression ); events.ByteProgression -= new ByteProgressionEventHandler( UpdateUI_ByteProgression ); } } catch( System.Reflection.TargetInvocationException except ) { MessageBox.Show( except.InnerException.Message, "Abort" ); } catch( Exception except ) { MessageBox.Show( except.Message, "Error" ); } finally { AbortButton.Enabled = false; StartButton.Enabled = true; m_abort = false; } }
Things to notice:
- I'm passing my "events" instance to:
- The ZipArchive's ctor. You could handle the ReadingZipItemProgression events.
- The AutoBatchUpdate ctor, which will in turn pass it to both BeginUpdate and EndUpdate. The later method will generate the GatheringZipContentByteProgression and BuildingZipByteProgression events.
- The CopyTo method. It will generate the ByteProgression events.
- I'm advising two times for the ByteProgression events, once for handling abort conditions, and another for updating my UI. This is a cool way to leverage delegates and separate the logic from the UI.
|
|
 Wednesday, May 11, 2005
I thought I'd give the new Yahoo Music Engine a try, so I downloaded the setup and launched the installation.
First, it comes with the Yahoo Messenger 6.0, something I don't want on my machine. I don't need it, I don't want to try it, I don't want to polute my machine with. But the installer won't give me the choice not to install it. I decide to continue installation anyway. At worst, I'll uninstall it later.
A following step introduces me with the Privacy Policy. It informs me that the Yahoo Music Engine will "collect certain information about your usage of the Music Engine, and will transmit such information back to Yahoo!".
Hmmm, wait a minute, where's the checkbox to disable this? Can't? Oh boy... They're asking me something big here. Usually, I can say "no" and try anyway (WinAmp, Windows Media Player). At least, those other apps are giving me the impression I have control over what info is sent.
Let's read this more carefully before taking the plunge. So I click the Yahoo Privay Center link.
[...] Such information may include the following: version number; unique identifier numbers that allow us to distinguish between different computers and different people using the Music Engine, Yahoo! ID, duration of session time; number of times you download a song.
They can link a unique computer-based number with a Yahoo! ID? Hmmm, I don't feel comfortable with that.
[...] The Music Engine may send additional information to better understand your music preferences, personalize certain content, recommend music to purchase or listen to, allow you to rate songs, share playlists, and to automatically identify what CD is in your computer. Information sent may include the song playing, your playlists and what CD is in your drive.
That's getting too much. If it's only for a better experience, and that cannot be tied to any advertising, then write it out loud in the setup!
Yahoo is a serious company. I should trust them they won't use that info against my needs, right? So I continue reading, with the remaining intention to install the engine.
By default, your music profile is public. It includes a description of your musical tastes, your ratings, followers and influencers, and lists of similar members. It will also link to your LAUNCHcast radio station.
Ok, that's it, I'm out. I'm forced into something I'm not comfortable with. I understand that in order to improve the online music industry, one must sometimes impose things. But in this case, Yahoo have reached my own limits of what I can trust, and what I want control over. Will there be an option somewhere in the engine to turn everything off? Maybe. But first impressions are important. I'll wait.
Conclusion: If the intentions behind this collection of information is only meant to improve my experience with the Yahoo Music Engine, and that I can disable this later, then the setup application should mention it. Can't disable this while installing? Fine! Then regain my confidence with a clear statement that this is for my sole benefits.
|
|
 Tuesday, May 03, 2005
Numbers can be very impressive. For example, FireFox recently hit the 50 million downloads mark. But what does that mean? How can we relate that number to the actual number of users? How do they count downloads?
Take me for example. I've installed FireFox both at home and work. So I count for two. But I've actually downloaded FireFox 8 times since its release, for every update there was (1.0, 1.0.1, 1.0.2 and 1.0.3), some through the automatic update, others from their web site. In any case, I'm sure both end-up being counted in that 50 million.
Jeremy has similar remarks concerning browser stats. Depending on the hosts, number of FireFox and IE users vary a lot. In some cases, the audience type explains it, but in other cases you just need to take numbers with a grain of salt.
Sure FireFox is gaining market shares. Sure FireFox has won me. I've also tried Opera 8, and it's a damn good browser, probably even better than FireFox, surely faster. But I can't stand the advertising banner, and why would I pay for no-ads Opera when I can find a free and almost as good alternative in FireFox?
I've paid for Trillian 1.0 Pro because at that time, there were no free runner-up. I've recently renewed for Trillian 3.x Pro because I just can't stand the ad banners in MSN Messenger, and didn't find something almost as good. Wrong. I did find a free alternative: Trillian Basic... but I couldn't live without a few features only in the pro version.
Tomorrow, I could convince myself I need Opera 8. Or IE 7 could win me back. By the way, I have downloaded Opera twice, to try it both at home and work. If one day they hit the 50 million downloads mark, you'll have to substract at least 2 from the actual number of users... Numbers are so volatile... Preferences are so fragile...
|
|
 Friday, April 22, 2005
You already know the original picture shown on Coding4Fun raised within me some concerns I was already outdated, old, unplugged, not into "it" anymore.
Well, I'm thinking of launching a new portal called Coding4Work, where developers will feel more connected to the real thing. Instead of being welcomed by the "Tattoo Dude":

You will be welcomed by the "Busy Dude":

Hmmm, that's much closer to my life, dude! 
|
|
|
|
|
|
|
|
Copyright © 2010 Xceed Software Inc.
|
|
|
|
Calendar
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 29 | 30 | 31 | 1 | 2 | 3 | 4 | | 5 | 6 | 7 | 8 | 9 | 10 | 11 | | 12 | 13 | 14 | 15 | 16 | 17 | 18 | | 19 | 20 | 21 | 22 | 23 | 24 | 25 | | 26 | 27 | 28 | 29 | 30 | 1 | 2 | | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| June, 2006 (1) |
| April, 2006 (2) |
| March, 2006 (2) |
| February, 2006 (2) |
| January, 2006 (4) |
| November, 2005 (3) |
| October, 2005 (3) |
| September, 2005 (6) |
| August, 2005 (2) |
| July, 2005 (4) |
| June, 2005 (4) |
| May, 2005 (3) |
| April, 2005 (10) |
| March, 2005 (4) |
| February, 2005 (4) |
| January, 2005 (3) |
| December, 2004 (4) |
| November, 2004 (5) |
| October, 2004 (4) |
| September, 2004 (7) |
|