Wednesday, December 17, 2008

Using the Proxy Pattern to Write to Multiple TextWriters

I was working on a data synchronizing application the other day.  I needed to write to a file for the export, write to a string builder for logging and analysis, and write to the console for debugging.  I know that it's pretty common that I'll need to write to more than 1 text stream at the same time, so I figured I could write a quick proxy application to write to a collection of TextWriters.Please comment on this post and let me know how this TextWriterProxy article helped you.Here's what I came up with:
    1 using System.Collections.Generic;
    2 using System.Text;
    3 using System.IO;
    4 
    5 namespace ESG.Utilities
    6 {
    7     public class TextWriterProxy : TextWriter
    8     {
    9         // store TextWriters here
   10         private List<TextWriter> _writers = new List<TextWriter>();
   11 
   12         #region Properties
   13 
   14         /// <summary>
   15         /// This property returns Encoding.Default.  The TextWriters in the
   16         /// TextWriterProxy collection can have any encoding.  However, this
   17         /// property is required.
   18         /// </summary>
   19         public override Encoding Encoding { get { return Encoding.Default; } }
   20 
   21         /// <summary>
   22         /// Gets or sets the line terminator string used by the TextWriters in
   23         /// the TextWriterProxy collection.
   24         /// </summary>
   25         public override string NewLine
   26         {
   27             get
   28             {
   29                 return base.NewLine;
   30             }
   31 
   32             set
   33             {
   34                 foreach (TextWriter tw in _writers)
   35                     tw.NewLine = value;
   36 
   37                 base.NewLine = value;
   38             }
   39         }
   40 
   41         #endregion
   42 
   43         #region Methods
   44 
   45         /// <summary>
   46         /// Add a new TextWriter to the TextWriterProxy collection.  Setting properties 
   47         /// or calling methods on the TextWriterProxy will perform the same action on 
   48         /// each TextWriter in the collection.
   49         /// </summary>
   50         /// <param name="writer">The TextWriter to add to the collection</param>
   51         public void Add(TextWriter writer)
   52         {
   53             // don't add a TextWriter that's already in the collection
   54             if (!_writers.Contains(writer))
   55                 _writers.Add(writer);
   56         }
   57 
   58         /// <summary>
   59         /// Remove a TextWriter from the TextWriterProxy collection.
   60         /// </summary>
   61         /// <param name="writer">The TextWriter to remove from the collection</param>
   62         /// <returns>True if the TextWriter was found and removed; False if not.</returns>
   63         public bool Remove(TextWriter writer)
   64         {
   65             return _writers.Remove(writer);
   66         }
   67 
   68 
   69         // this is the only Write method that needs to be overridden
   70         // because all of the Write methods in a TextWriter ultimately
   71         // end up calling Write(char)
   72 
   73         /// <summary>
   74         /// Write a character to the text stream of each TextWriter in the 
   75         /// TextWriterProxy collection.
   76         /// </summary>
   77         /// <param name="value">The char to write</param>
   78         public override void Write(char value)
   79         {
   80             foreach (TextWriter tw in _writers)
   81                 tw.Write(value);
   82 
   83             base.Write(value);
   84         }
   85 
   86         /// <summary>
   87         /// Closes the TextWriters in the TextWriterProxy as well as the 
   88         /// TextWriterProxy instance and releases any system resources
   89         /// associated with them.
   90         /// </summary>
   91         public override void Close()
   92         {
   93             foreach (TextWriter tw in _writers)
   94                 tw.Close();
   95 
   96             base.Close();
   97         }
   98 
   99         /// <summary>
  100         /// Releases all resources used by the TextWriterProxy and by the 
  101         /// TextWriters in the TextWriterProxy collection. 
  102         /// </summary>
  103         /// <param name="disposing">Pertains only to the TextWriterProxy instance: 
  104         /// true to release both managed and unmanaged resources; false to release 
  105         /// only unmanaged resources.</param>
  106         protected override void Dispose(bool disposing)
  107         {
  108             foreach (TextWriter tw in _writers)
  109                 tw.Dispose();
  110 
  111             base.Dispose(disposing);
  112         }
  113 
  114         /// <summary>
  115         /// Clears all buffers for each TextWriter in the TextWriterProxy 
  116         /// collection and causes all buffered data to be written
  117         /// to the underlying device.
  118         /// </summary>
  119         public override void Flush()
  120         {
  121             foreach (TextWriter tw in _writers)
  122                 tw.Flush();
  123 
  124             base.Flush();
  125         }
  126 
  127         #endregion
  128     }
  129 }

So far, it works great. It cleans up a lot of my code and gives me the option to write to any number of TextWriters with only one call. Further, if you are calling a method that takes a TextWriter as a parameter, you can pass the TextWriterProxy to it because it extends the TextWriter class. Here's what the usage syntax looks like:
    1 // create a TextWriterProxy instance
    2 TextWriterProxy proxy = new TextWriterProxy();
    3 
    4 // add the Console.Out TextWriter
    5 proxy.Add(Console.Out);
    6 
    7 // you can still write directly to console
    8 Console.WriteLine(string.Empty.PadRight(80, '='));
    9 
   10 // add a StreamWriter for a FileStream
   11 FileStream fs = new FileStream("C:\\TestExportFileAutoGen.abx", FileMode.Create);
   12 StreamWriter resultWriter = new StreamWriter(fs);
   13 proxy.Add(resultWriter);
   14 
   15 // add a StringWriter for a StringBuilder
   16 StringBuilder sb = new StringBuilder();
   17 StringWriter resultStringWriter = new StringWriter(sb);
   18 proxy.Add(resultStringWriter);
   19 
   20 // call a method that takes a TextWriter
   21 ClientSync.GenerateSessionDataExport("Sync.ServerExport", proxy);
   22 
   23 // write directly to the TextWriterProxy
   24 proxy.WriteLine("Export Complete!");
   25 
   26 // close all of my writers
   27 proxy.Close();

And there you have it. A TextWriterProxy class to write to multiple TextWriters at once.

Thursday, December 4, 2008

An Online Image Thumbnailer

Hey folks,

I've published a small online image thumbnail utility. If it proves useful (without bogging down our servers), I'll leave it up for public consumption. If you're interested in reading a little more about it, you can find the story below. If you'd just like to see the utility, visit D. Patrick Caldwell's Image Thumbnailer.

So, there I was, trying to get a background off of my Picasa Web Albums to put on my iPhone. In the iPhone Safari browser, you can save images by holding your finger on the image until a save dialog box comes up. Problem is, Picasa has somehow (somewhy) disabled it. So, I figured a bookmarklet would help me out. I could then link directly to the image and all of the Picasa scripts would be gone.

I found a few bookmarklets that displayed all of the images in the page, but they all open in the current window and I wanted a new window (and some nice formatting wouldn't hurt either). So, I looked for an online image thumbnailer and couldn't find one. About 30 minutes later, I had a thumbnailer. An hour after that, I had my bookmarklets (and a new background incidentally). I added a little error handling and some logging and had a fully functional service in about 2 hours.

Then I spent about 8 hours styling the welcome page :).

In any event, here's what my Thumbnailer does. I can take any image anywhere on the net (well, most anywhere . . . I have to have access to the image), and produce a thumbnail with one or two constraints: maximum width and maximum height. Here's an example of the same image scaled to 4 different heights:








The same thing works for widths:








Finally, you can specify both width and height and it'll use the most restrictive parameter.