r3plica
r3plica

Reputation: 13367

File access design pattern

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

Answers (1)

Travis
Travis

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

Related Questions