Archive

Posts Tagged ‘ZIP’

Workarround: IE11 changing download file extension to .zip

At ClipFlair Gallery, apart from opening a ClipFlair activity in ClipFlair Studio, downloading of an activity (.clipflair) file is also supported.

However, because the component serialization file format of ClipFlair Studio is XML plus media assets packed in .zip archive (with nesting allowed, where components and whole activities can be placed in other activities), Internet Explorer 11 (and probably other browsers too) was downloading .clipflair files as .zip (changing their file extension).

At first, I thought that occured because I was using MIME type “application/zip” at the IIS web server/site settings for that file extension. So then I tried to change it to “application/octet-stream” hoping that one would be treated as an “opaque” data stream.

However, eventually I ended up setting a custom MIME type “application/clipflair” for the file extension “.clipflair”, because even with “application/octet-stream” (as with the “application/zip” that I had before), IE11 was still saving the .clipflair file as .zip (obviously detecting the zip content in the download stream).

<?xml version=”1.0″?>
<configuration>

<system.webServer>

<directoryBrowse enabled=”true” showFlags=”Size, Extension”/>

<defaultDocument>
<files>
<clear/>
<add value=”index.html”/>
<add value=”Default.aspx”/>
<add value=”Default.html”/>
</files>
</defaultDocument>

<caching>
<profiles>
<add extension=”.log”
policy=”CacheUntilChange”
kernelCachePolicy=”CacheUntilChange”/>
</profiles>
</caching>

<staticContent>
<mimeMap fileExtension=”.log” mimeType=”text/plain”/>
<mimeMap fileExtension=”.clipflair” mimeType=”application/clipflair”/>
<mimeMap fileExtension=”.dzi” mimeType=”text/xml”/>
<mimeMap fileExtension=”.dzc” mimeType=”text/xml”/>
<mimeMap fileExtension=”.cxml” mimeType=”text/xml”/>
</staticContent>

</system.webServer>

<system.web>
<compilation debug=”true” targetFramework=”4.0.3″/>
</system.web>

</configuration>

HowTo: format XML output of DataContractSerializer

based on the other samples posted at StackOverflow on how to format XML created by DataContractSerializer, that use XmlWriter, here’s a version (from ClipFlair source code) that works with streams (and Ionic.Zip library in specific).

It also shows how the code is when you don’t apply formatting (using conditional compilation).  Just comment out the #define (prefix it with //) to make it write unformatted XML.

 

#define WRITE_FORMATTED_XML

using System.Xml;

namespace ClipFlair.Windows
{

  public partial class BaseWindow : FloatingWindow
  {

    //...

    #if WRITE_FORMATTED_XML
    private static XmlWriterSettings XML_WRITER_SETTINGS = 
new XmlWriterSettings() { Indent=true, IndentChars=" "}; #endif //... public virtual void SaveOptions(ZipFile zip, string zipFolder = "")
//THIS IS THE CORE SAVING LOGIC { if (SavingOptions != null) SavingOptions(this, null); //notify any listeners View.Busy = true; try { ZipEntry optionsXML =
zip.AddEntry(zipFolder + "/" + View.GetType().FullName + ".options.xml", new WriteDelegate((entryName, stream) => { DataContractSerializer serializer =
new DataContractSerializer(View.GetType());
//assuming current View isn't null #if WRITE_FORMATTED_XML using (XmlWriter writer = XmlWriter.Create(stream, XML_WRITER_SETTINGS)) serializer.WriteObject(writer, View); #else serializer.WriteObject(stream, View); #endif })); } catch (Exception e) { MessageBox.Show("ClipFlair options save failed: " + e.Message); } finally { View.Busy = false; //in any case (error or not) clear the Busy flag } if (SavedOptions != null) SavedOptions(this, null); //notify any listeners } //... } }

HowTo: Use DotNetZip (Ionic.Zip) library in Silverlight

If you want to use the latest DotNetZip library (version 1.9.1.8) with Silverlight, you should get a patched version from http://dotnetzip.codeplex.com/workitem/14049, instead of the official download for Silverlight.

It seems the Silverlight build in 1.9.1.8 official download doesn’t work correctly, since it tries to get IBM437 Encoding (as the original ZIP spec required) from Silverlight, but that encoding is not available via Encoding.GetEncoding method in Silverlight (neither encoder and decoder fallbacks for GetEncoding are available as in .NET 4.5).

So the patched version uses Unicode encoding instead (only UTF-8 and Unicode [UTF-16] Encodings are available in Silverlight), although it could have also used a text encoding class generator for the missing encoding and change the source code similar to how Mike Taulty had done with an older version of DotNetZip.

I do prefer the Unicode approach, since using the IBM437 codepage would have issues with Unicode filenames. ZIPs with such filenames are supported fine by most modern ZIP viewers, including Windows Explorer’s Compressed Folders views in recent versions of Windows.

HowTo: Write directly into a ZIP stream using DotNetZip

At ClipFlair’s Playground Silverlight app I’m using DotNetZip library (Ionic.Zip namespace) for writing saved component state to a .ZIP file. In fact the Activity container there can create one .ZIP for each state that contains one .ZIP per-component it hosts (it can even host multiple instances of its own Activity component type to have nested Activities in its ZUI interface).

DotNetZip is a perfect match for Silverlight’s security restrictions, where OpenFileDialog and SaveFileDialog only give you a stream to work with (unless your app has been granted elevated trust), since apart from working with filesystem files and folders it allows you to also give it content to compress in the form of a stream.

Note: the latest DotNetZip 1.9.1.8 official version for Silverlight doesn’t work correctly – you need a patched build, see: https://zoomicon.wordpress.com/2012/11/25/howto-use-dotnetzip-ionic-zip-library-in-silverlight/

For working with streams at DotNetZip there are two options (as explained at http://dotnetzip.codeplex.com/discussions/231712):

1) pass it an in-memory buffer (this is a non-optimal method)

public override void SaveOptions(ZipFile zip, string zipFolder = "")  
{
base.SaveOptions(zip, zipFolder);
MemoryStream
stream = new MemoryStream(); //non-optimal, uses memory buffer
gridCaptions.WriteCaptions(stream, "captions.srt"); stream.Position = 0;
zip.AddEntry(zipFolder + "/captions.srt", stream);
}

2) the other is to pass it a write delegate for a method that writes the data to a stream that DotNetZip will pass to it when needed.

public override void SaveOptions(ZipFile zip, string zipFolder = "")
{
  base.SaveOptions(zip, zipFolder);
  zip.AddEntry(zipFolder + "/captions.srt", SaveCaptions);
}

public void SaveCaptions(string entryName, Stream stream)
{
  gridCaptions.WriteCaptions(stream, entryName);
}

 

In case one needs to pass more parameters (apart from the zip entry name and the stream that DotNetZip passes to the delegate), they could instantiate a class instance and keep the parameter there, then pass a method of that class via the delegate to DotNetZip. An easier way is to use an anonymous method and define it in a context where the parameter is available, e.g. as a local variable.

public override void SaveOptions(ZipFile zip, string zipFolder = "")
{
  base.SaveOptions(zip, zipFolder);
  foreach (BaseWindow window in activity.Windows)
    SaveWindow(zip, zipFolder, window);
}

private static void SaveWindow(ZipFile zip, string zipFolder, BaseWindow window)
{
  string title = ((string)window.Title).TrimStart(); 
//using TrimStart() to not have filenames start with
//space chars in case it's an issue with ZIP spec
if (title == "") title = window.GetType().Name; zip.AddEntry( zipFolder + "/" + title + " - " + Guid.NewGuid() + ".clipflair.zip", new WriteDelegate((entryName, stream) => { window.SaveOptions(stream); }) );
//save ZIP file for child window }
%d bloggers like this: