Reputation: 13367
I have a class (currently called FileHandler) which handles files. It looks something like this:
public class FileHandler : IDisposable
{
private readonly IObjectService service;
private readonly string destination;
private readonly string fileName;
private readonly string fullPath;
private Metadata metadata;
public FileHandler(IObjectService service, string destination, string fileName)
{
this.service = service;
this.destination = destination;
this.fileName = fileName;
this.fullPath = Path.Combine(destination, fileName);
}
public XDocument GetMetadata(string exifToolPath)
{
if (!File.Exists(this.fullPath))
throw new FileNotFoundException();
return new XDocument(GetFullXml(exifToolPath));
}
public string GetThumbnail()
{
this.service.Download(this.destination, this.fileName); // Get our file
if (File.Exists(this.fullPath))
{
//if (this.metadata != null)
//{
//}
return null;
//using (var fs = File.OpenRead(this.fullPath))
//{
//}
}
return "";
}
public void Dispose()
{
this.service.Dispose();
}
private XElement GetFullXml(string exifToolPath)
{
string args = string.Format("-X \"{0}\"", this.fullPath);
string output = RunProcess(exifToolPath, args);
output = Sanitize(output);
return new XElement("FullMetadata", XElement.Parse(output));
}
private string RunProcess(string exifToolPath, string args)
{
if (String.IsNullOrEmpty(exifToolPath))
throw new SystemException("EXIFTool Executable Path Not Configured");
if (!File.Exists(exifToolPath))
throw new SystemException("EXIFTool Executable Not Found: " + exifToolPath);
var process = new Process {
StartInfo = {
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
FileName = exifToolPath,
Arguments = args }
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output;
}
private string Sanitize(string s)
{
return s.Replace("&", string.Empty);
}
}
Now, I struggle with naming my classes. Recently I started using the Service/Repository design pattern so all my classes were either Repositories or Services which made naming them easy :)
Now I want to do the same with my file handler.
This is my need:
I download the file from an amazon bucket I need to get it's metadata based on the file mime type I create a thumbnail (images are just resized and saved, videos use ffmpeg and everything else uses a default thumbnail based on the extension)
So, what design pattern fits my needs is my question?
I tried using the Strategy pattern but the problem is the concrete classes need to be created after I get the metadata.
Please help :)
Update 1
Ok, so no one answered, so here is a bit of code I wrote to address the situation. Can someone check it and make sure I am not being thick?
So, I created a base File like this:
public class File : IDisposable
{
public readonly IObjectService service;
public readonly string destination;
public readonly string fileName;
public readonly string fullPath;
private XNamespace SystemNamespace = "http://ns.exiftool.ca/File/System/1.0/";
private XNamespace FileNamespace = "http://ns.exiftool.ca/File/1.0/";
private XNamespace Composite = "http://ns.exiftool.ca/Composite/1.0/";
public XDocument Document { get; set; }
public File(IObjectService service, string destination, string fileName)
{
this.service = service;
this.destination = destination;
this.fileName = fileName;
this.fullPath = Path.Combine(destination, fileName);
}
public XDocument GenerateMetadataFromFile(string exifToolPath)
{
if (!System.IO.File.Exists(this.fullPath))
throw new FileNotFoundException();
return new XDocument(GetFullXml(exifToolPath));
}
public virtual Metadata GetMetadata(string exifToolPath)
{
this.Document = this.GenerateMetadataFromFile(exifToolPath);
var metadata = new Metadata()
{
ReferenceId = this.GenerateId(),
FileSize = (string)Document.Descendants(SystemNamespace + "FileSize").FirstOrDefault(),
FileType = (string)Document.Descendants(FileNamespace + "FileType").FirstOrDefault(),
MIMEType = (string)Document.Descendants(FileNamespace + "MIMEType").FirstOrDefault(),
};
return metadata;
}
public virtual string GetThumbnail()
{
var iconPath = Resources.Resources.DocumentIconPath;
var iconName = GetDocumentIcon(System.IO.Path.GetExtension(this.fileName));
var iconBasePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, iconPath.Substring(1).Replace("/", "\\"), iconName);
return iconBasePath;
}
public string GetDocumentIcon(string ext)
{
string fileName = "txt.png";
switch (ext)
{
case ".ai":
fileName = "ai.png";
break;
case ".avi":
fileName = "avi.png";
break;
case ".doc":
fileName = "doc.png";
break;
case ".docx":
fileName = "doc.png";
break;
case ".fla":
fileName = "fla.png";
break;
case ".id":
fileName = "id.png";
break;
case ".jpg":
fileName = "jpg.png";
break;
case ".jpeg":
fileName = "jpeg.png";
break;
case ".pdf":
fileName = "pdf.png";
break;
case ".ppt":
fileName = "ppt.png";
break;
case ".pptx":
fileName = "ppt.png";
break;
case ".psd":
fileName = "psd.png";
break;
case ".txt":
fileName = "txt.png";
break;
case ".wav":
fileName = "wav.png";
break;
case ".xls":
fileName = "xls.png";
break;
case ".xlsx":
fileName = "xls.png";
break;
case "video":
fileName = "video.png";
break;
}
return fileName;
}
private XElement GetFullXml(string exifToolPath)
{
string args = string.Format("-X \"{0}\"", this.fullPath);
string output = RunProcess(exifToolPath, args);
output = Sanitize(output);
return new XElement("FullMetadata", XElement.Parse(output));
}
public virtual string GetCreateDate()
{
if (this.Document.Descendants(Composite + "DateTimeCreated").FirstOrDefault() != null)
return (string)this.Document.Descendants(Composite + "DateTimeCreated").FirstOrDefault();
return null;
}
public virtual string GetModifyDate()
{
return null;
}
public virtual string GetDuration()
{
if (this.Document.Descendants(Composite + "Duration").FirstOrDefault() != null)
return (string)this.Document.Descendants(Composite + "Duration").FirstOrDefault();
return null;
}
public virtual int GetImageWidth()
{
if (this.Document.Descendants(FileNamespace + "ImageWidth").FirstOrDefault() != null)
return (int)this.Document.Descendants(FileNamespace + "ImageWidth").FirstOrDefault();
return 0;
}
public virtual int GetImageHeight()
{
if (this.Document.Descendants(FileNamespace + "ImageHeight").FirstOrDefault() != null)
return (int)this.Document.Descendants(FileNamespace + "ImageHeight").FirstOrDefault();
return 0;
}
public virtual string GetImageSize()
{
if (this.Document.Descendants(Composite + "ImageSize").FirstOrDefault() != null)
return (string)this.Document.Descendants(Composite + "ImageSize").FirstOrDefault();
return null;
}
public void Dispose()
{
this.service.Dispose();
}
private string GenerateId()
{
long i = 1;
foreach (byte b in Guid.NewGuid().ToByteArray())
{
i *= ((int)b + 1);
}
return string.Format("{0:x}", i - DateTime.Now.Ticks);
}
private string RunProcess(string exifToolPath, string args)
{
if (String.IsNullOrEmpty(exifToolPath))
throw new SystemException("EXIFTool Executable Path Not Configured");
if (!System.IO.File.Exists(exifToolPath))
throw new SystemException("EXIFTool Executable Not Found: " + exifToolPath);
var process = new Process
{
StartInfo =
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
FileName = exifToolPath,
Arguments = args
}
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output;
}
private string Sanitize(string s)
{
return s.Replace("&", string.Empty);
}
}
then I created a simple Document class which inherits File
public class Document : File
{
private XNamespace XMPxmp = "http://ns.exiftool.ca/XMP/XMP-xmp/1.0/";
private XNamespace PDF = "http://ns.exiftool.ca/PDF/PDF/1.0/";
private XNamespace FlashPix = "http://ns.exiftool.ca/FlashPix/FlashPix/1.0/";
private XNamespace XML = "http://ns.exiftool.ca/XML/XML/1.0/";
public Document(IObjectService service, string destination, string fileName)
: base(service, destination, fileName)
{
}
public override Metadata GetMetadata(string exifToolPath)
{
var metadata = base.GetMetadata(exifToolPath);
metadata.CreateDate = this.GetCreateDate();
metadata.ModifyDate = this.GetModifyDate();
return metadata;
}
public override string GetThumbnail()
{
return base.GetThumbnail();
}
public override string GetCreateDate()
{
if (Document.Descendants(PDF + "CreateDate").FirstOrDefault() != null)
return (string)Document.Descendants(PDF + "CreateDate").FirstOrDefault();
if (Document.Descendants(XMPxmp + "CreateDate").FirstOrDefault() != null)
return (string)Document.Descendants(XMPxmp + "CreateDate").FirstOrDefault();
if (Document.Descendants(FlashPix + "CreateDate").FirstOrDefault() != null)
return (string)Document.Descendants(FlashPix + "CreateDate").FirstOrDefault();
if (Document.Descendants(XML + "CreateDate").FirstOrDefault() != null)
return (string)Document.Descendants(XML + "CreateDate").FirstOrDefault();
return base.GetCreateDate();
}
public override string GetModifyDate()
{
if (Document.Descendants(PDF + "ModifyDate").FirstOrDefault() != null)
return (string)Document.Descendants(PDF + "ModifyDate").FirstOrDefault();
if (Document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault() != null)
return (string)Document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault();
if (Document.Descendants(FlashPix + "ModifyDate").FirstOrDefault() != null)
return (string)Document.Descendants(FlashPix + "ModifyDate").FirstOrDefault();
if (Document.Descendants(XML + "ModifyDate").FirstOrDefault() != null)
return (string)Document.Descendants(XML + "ModifyDate").FirstOrDefault();
return base.GetModifyDate();
}
}
and then I created a complicated Image class which also inherits File:
public class Image : File
{
private XNamespace PNG = "http://ns.exiftool.ca/PNG/PNG/1.0/";
private XNamespace GIF = "http://ns.exiftool.ca/GIF/GIF/1.0/";
private XNamespace IFD0 = "http://ns.exiftool.ca/EXIF/IFD0/1.0/";
private XNamespace IFD1 = "http://ns.exiftool.ca/EXIF/IFD1/1.0/";
private XNamespace BMP = "http://ns.exiftool.ca/BMP/BMP/1.0/";
private XNamespace JFIF = "http://ns.exiftool.ca/JFIF/JFIF/1.0/";
private XNamespace XMPtiff = "http://ns.exiftool.ca/XMP/XMP-tiff/1.0/";
private XNamespace XMPxmp = "http://ns.exiftool.ca/XMP/XMP-xmp/1.0/";
public Image(IObjectService service, string destination, string fileName)
: base(service, destination, fileName)
{
}
public override string GetThumbnail()
{
this.service.Download(this.destination, this.fileName); // Get our file
if (System.IO.File.Exists(this.fullPath))
{
var ext = Path.GetExtension(this.fileName);
var iconName = this.fileName.Replace(ext, "_thumb.png");
var iconPath = Path.Combine(this.destination, iconName); // get our final path
using (var original = new Bitmap(this.fullPath))
{
var thumnail = ResizeImage(300, original); // Resize our image
thumnail.Save(iconPath, System.Drawing.Imaging.ImageFormat.Png); // save the thumbnail
return iconPath;
}
}
return "";
}
public override Metadata GetMetadata(string exifToolPath)
{
var metadata = base.GetMetadata(exifToolPath);
metadata.CreateDate = this.GetCreateDate();
metadata.ModifyDate = this.GetModifyDate();
metadata.ImageWidth = this.GetImageWidth();
metadata.ImageHeight = this.GetImageHeight();
metadata.ImageSize = this.GetImageSize();
metadata.Orientation = (string)this.Document.Descendants(XMPtiff + "Orientation").FirstOrDefault();
return metadata;
}
public override string GetCreateDate()
{
if (this.Document.Descendants(XMPxmp + "CreateDate").FirstOrDefault() != null)
return (string)this.Document.Descendants(XMPxmp + "CreateDate").FirstOrDefault();
return base.GetCreateDate();
}
public override string GetModifyDate()
{
if (this.Document.Descendants(IFD0 + "ModifyDate").FirstOrDefault() != null)
return (string)Document.Descendants(IFD0 + "ModifyDate").FirstOrDefault();
if (this.Document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault() != null)
return (string)this.Document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault();
return base.GetModifyDate();
}
public override int GetImageWidth()
{
if (this.Document.Descendants(PNG + "ImageWidth").FirstOrDefault() != null)
return (int)this.Document.Descendants(PNG + "ImageWidth").FirstOrDefault();
if (this.Document.Descendants(GIF + "ImageWidth").FirstOrDefault() != null)
return (int)this.Document.Descendants(GIF + "ImageWidth").FirstOrDefault();
if (this.Document.Descendants(IFD0 + "ImageWidth").FirstOrDefault() != null)
return (int)this.Document.Descendants(IFD0 + "ImageWidth").FirstOrDefault();
if (this.Document.Descendants(BMP + "ImageWidth").FirstOrDefault() != null)
return (int)this.Document.Descendants(BMP + "ImageWidth").FirstOrDefault();
return base.GetImageWidth();
}
public override int GetImageHeight()
{
if (this.Document.Descendants(PNG + "ImageHeight").FirstOrDefault() != null)
return (int)this.Document.Descendants(PNG + "ImageHeight").FirstOrDefault();
if (this.Document.Descendants(GIF + "ImageHeight").FirstOrDefault() != null)
return (int)this.Document.Descendants(GIF + "ImageHeight").FirstOrDefault();
if (this.Document.Descendants(IFD0 + "ImageHeight").FirstOrDefault() != null)
return (int)this.Document.Descendants(IFD0 + "ImageHeight").FirstOrDefault();
if (this.Document.Descendants(BMP + "ImageHeight").FirstOrDefault() != null)
return (int)this.Document.Descendants(BMP + "ImageHeight").FirstOrDefault();
return base.GetImageHeight();
}
public override string GetImageSize()
{
if (this.Document.Descendants(PNG + "ImageSize").FirstOrDefault() != null)
return (string)this.Document.Descendants(PNG + "ImageSize").FirstOrDefault();
if (this.Document.Descendants(GIF + "ImageSize").FirstOrDefault() != null)
return (string)this.Document.Descendants(GIF + "ImageSize").FirstOrDefault();
if (this.Document.Descendants(IFD0 + "ImageSize").FirstOrDefault() != null)
return (string)this.Document.Descendants(IFD0 + "ImageSize").FirstOrDefault();
if (this.Document.Descendants(BMP + "ImageSize").FirstOrDefault() != null)
return (string)this.Document.Descendants(BMP + "ImageSize").FirstOrDefault();
return base.GetImageSize();
}
private int GetXResolution()
{
if (this.Document.Descendants(JFIF + "XResolution").FirstOrDefault() != null)
return (int)this.Document.Descendants(JFIF + "XResolution").FirstOrDefault();
if (this.Document.Descendants(IFD1 + "XResolution").FirstOrDefault() != null)
return (int)this.Document.Descendants(IFD1 + "XResolution").FirstOrDefault();
if (this.Document.Descendants(XMPtiff + "XResolution").FirstOrDefault() != null)
return (int)this.Document.Descendants(XMPtiff + "XResolution").FirstOrDefault();
return 0;
}
private int GetYResolution()
{
if (this.Document.Descendants(JFIF + "YResolution").FirstOrDefault() != null)
return (int)this.Document.Descendants(JFIF + "YResolution").FirstOrDefault();
if (this.Document.Descendants(IFD1 + "YResolution").FirstOrDefault() != null)
return (int)this.Document.Descendants(IFD1 + "YResolution").FirstOrDefault();
if (this.Document.Descendants(XMPtiff + "YResolution").FirstOrDefault() != null)
return (int)this.Document.Descendants(XMPtiff + "YResolution").FirstOrDefault();
return 0;
}
private string GetResolutionUnit()
{
if (this.Document.Descendants(JFIF + "ResolutionUnit").FirstOrDefault() != null)
return (string)this.Document.Descendants(JFIF + "ResolutionUnit").FirstOrDefault();
if (this.Document.Descendants(XMPtiff + "ResolutionUnit").FirstOrDefault() != null)
return (string)this.Document.Descendants(XMPtiff + "ResolutionUnit").FirstOrDefault();
return null;
}
private Stream ResizeImage(int width, System.Drawing.Image original)
{
var stream = new MemoryStream();
var ext = Path.GetExtension(this.fileName);
var image = original;
var maxHeight = original.Height;
var maxWidth = original.Width;
var b = maxHeight > maxWidth ? maxHeight : maxWidth;
var percent = (b > width) ? (width * 1.0) / b : 1.0;
maxHeight = (int)(maxHeight * percent);
maxWidth = (int)(maxWidth * percent);
if (original.Height < maxHeight && original.Width < maxWidth)
{
// do nothing
}
else
{
var cpy = new System.Drawing.Bitmap(maxWidth, maxHeight, PixelFormat.Format32bppArgb);
using (var gr = Graphics.FromImage(cpy))
{
gr.Clear(Color.Transparent);
// This is said to give best quality when resizing images
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.DrawImage(original,
new Rectangle(0, 0, maxWidth, maxHeight),
new Rectangle(0, 0, original.Width, original.Height),
GraphicsUnit.Pixel);
}
image = cpy;
}
switch (ext.ToLower())
{
case ".tif":
image.Save(stream, System.Drawing.Imaging.ImageFormat.Tiff); // save the thumbnail
break;
case ".tiff":
image.Save(stream, System.Drawing.Imaging.ImageFormat.Tiff); // save the thumbnail
break;
case ".bmp":
image.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); // save the thumbnail
break;
case ".gif":
image.Save(stream, System.Drawing.Imaging.ImageFormat.Gif); // save the thumbnail
break;
case ".jpg":
image.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg); // save the thumbnail
break;
case ".jpeg":
image.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg); // save the thumbnail
break;
default:
image.Save(stream, System.Drawing.Imaging.ImageFormat.Png); // save the thumbnail
break;
}
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
}
and then finally I created a FileFactory class which looks like this:
public class FileFactory
{
public static File Get(IObjectService service, string destination, string fileName, string exifToolPath)
{
var file = new File(service, destination, fileName);
var metadata = file.GetMetadata(exifToolPath);
var mime = metadata.MIMEType;
var type = mime.ToLower().Split('/')[0];
switch(type)
{
case "image":
return file as Image;
default:
return file as Document;
}
}
}
Hopefully now you can see what I am trying to achieve. I have 4 classes that will inherit from File (Document, Audio, Video and Image). Each of these classes will override methods from File.
Now, looking at my factory I create an instance of the generic File class and by checking the metadata MIMEType I return the instance as an Image or Document so that elsewhere in my code, when I call the method GetThumbnail() it uses the correct code to generate that thumbnail.
Would you class this as efficient? Or would you suggest an alternative method? Please help me :)
Upvotes: 1
Views: 928
Reputation: 10547
My answer is to pick something, then be willing to change it later. Getting stuck on the perfect name doesn't help. Renaming is just a small refactor away and your tools help. Context appears that you didn't have before.
Upvotes: 1