zoukankan      html  css  js  c++  java
  • 下载文件时根据MIME类型自动判断保存文件的扩展名

    mime 引言

    用WebClient下载远程资源时,经常会遇到类似这样的网址:

    http://www.uushare.com/filedownload?user=icesee&id=2205188

    http://www.guaishow.com/u/luanfujie/g9675/

    我们不知道这个Url具体代表的是一个网页,还是某种类型的文件。

    而有些Url虽然带有扩展名,但可能是错误的扩展名,常见的比如把gif文件标上了jpg扩展名。

    如果我们没法正确判断下载源的文件类型的话,就无法保存为正确的文件格式,会给后续操作及人工阅览造成困扰。

    所幸的是,WebRequest可以给出下载源的MIME信息,这让我们可以确定文件的真实格式,并以此来决定最终的存储扩展名。(MIME是什么?

    建立MIME映射字典

    我们首先需要做的工作就是建立一个MIME类型到其对应扩展名的映射字典。

    我从网上找来了一个MIME类型列表,并通过正则表达式将其转换为程序代码,粘入了程序中:

     image

    这个通过正则表达式转换而来的代码量非常大。

    需注意的是,其中有很多MIME类型相同但扩展名不同的数据,我们在添加到字典时就将多余的不必要记录忽略了,比如高亮处的那三条都是audio/x-aiff类型,那么后两个扩展名都不会添加到字典中,也不会在后续的操作中被使用。

    如果你觉得有些类型添加的对应扩展名不是最常见的对应类型的话,就得手动调整代码了。(下文中就出现了这种情况,如text/html对应的是dhtml扩展名,image/jpeg对应的是jpe扩展名)

    字典构建完毕之后,就可以通过这样一个方法来获取MIME类型所对应的扩展名了:

    string 获取对应扩展名(string ContentType)

    {

        foreach (var f in MimeDic.Keys)

        {

            if (ContentType.ToLower().IndexOf(f) >= 0) return MimeDic[f];

        }

        return null;

    }

    这里之所以使用IndexOf方法判断,是因为传入的ContentType中可能还包含其他信息,比如编码格式。

    题外话:看到网上曾有人抱怨说WebClient下载网页时容易产生乱码,而且又不好读取网页的编码格式,其实WebRequest的ContentType中就包含MIME和编码格式信息:

    image

    生成下载文件路径

    现在有了上面的方法,我们就可以通过MIME类型确定文件的扩展名了。

    现在我们将书写一个用于生成下载文件路径的方法,其功能为:

    1. 分析文件的源Url,将其文件名部分作为下载文件的文件名。
    2. 如果其Url中不含文件名部分(域名或目录形式),则以其目录名为下载文件的文件名。
    3. 根据传入的MIME类型自动确定并替换Url中的原始扩展名(如果有的话),以用作下载文件的文件名。
    4. 判断传入的存储目录中是否已存在与下载文件名相同的文件,存在的话就进行重命名,直到没有同名文件为止。

    功能有点多了,不适合做范例,不过还是很实用的,所以这里就顺道分享出来。

    其代码为:

    string 生成下载文件存放路径(string 存放目录, Uri Uri, string ContentType)

    {

        var ex = 获取对应扩展名(ContentType);

        string up = null;

        string upne = null;

        if (Uri.LocalPath == "/")

        {

            //处理Url是域名的情况

            up = upne = Uri.Host;

        }

        else

        {

            if (Uri.LocalPath.EndsWith("/"))

            {

                //处理Url是目录的情况

                up = Uri.LocalPath.Substring(0, Uri.LocalPath.Length - 1);

                upne = Path.GetFileName(up);

            }

            else

            {

                //处理常规Url

                up = Uri.LocalPath;

                upne = Path.GetFileNameWithoutExtension(up);

            }

        }

        var name = string.IsNullOrEmpty(ex) ? Path.GetFileName(up) : upne + "." + ex;

        var fn = Path.Combine(存放目录, name);

        var x = 1;

        while (File.Exists(fn))

        {

            fn = Path.Combine(存放目录, Path.GetFileNameWithoutExtension(name) + "(" + x++ + ")" + Path.GetExtension(name));

        }

        return fn;

    }

    为了验证其效果,我们通过一个单元测试进行评测:

    [TestMethod]

    public void 文件名生成测试()

    {

        var d = @"C:\Users\Public\Downloads";

        //gif格式文件,正常下载

        Assert.AreEqual(@"C:\Users\Public\Downloads\35ad5275ed17904d4a2d40f3dacea80b.gif", 生成下载文件存放路径(d, new Uri("http://i3.6.cn/cvbnm/7c/15/a3/35ad5275ed17904d4a2d40f3dacea80b.gif"), "image/gif"));

        //url中扩展名是gif,但MIME类型实际是image/jpeg的资源。下载后的扩展名是jpe,因为字典MimeDic里存储的对应扩展名就是jpe

        Assert.AreEqual(@"C:\Users\Public\Downloads\35ad5275ed17904d4a2d40f3dacea80b.jpe", 生成下载文件存放路径(d, new Uri("http://i3.6.cn/cvbnm/7c/15/a3/35ad5275ed17904d4a2d40f3dacea80b.gif"), "image/jpeg"));

        //一个带参数的网页url。下载后的扩展名是dhtml,因为字典MimeDic里存储的对应扩展名就是dhtml

        Assert.AreEqual(@"C:\Users\Public\Downloads\filedownload.dhtml", 生成下载文件存放路径(d, new Uri("http://www.uushare.com/filedownload?user=icesee&id=2205188"), "text/html"));

        //一个网页url,其格式为目录形式的,没有确切文件名。

        Assert.AreEqual(@"C:\Users\Public\Downloads\g9675.dhtml", 生成下载文件存放路径(d, new Uri("http://www.guaishow.com/u/luanfujie/g9675/"), "text/html"));

        //域名形式

        Assert.AreEqual(@"C:\Users\Public\Downloads\www.g.cn.dhtml", 生成下载文件存放路径(d, new Uri("http://www.g.cn/"), "text/html"));

        Assert.AreEqual(@"C:\Users\Public\Downloads\g.cn.dhtml", 生成下载文件存放路径(d, new Uri("http://g.cn"), "text/html"));

    }

    文件下载

    万事俱备,只欠东风了,让我们来完成下载方法:

    /// <summary>

    /// 下载文件到指定目录,并返回下载后存放的文件路径

    /// </summary>

    /// <param name="Uri">网址</param>

    /// <param name="存放目录">存放目录,如果该目录中已存在与待下载文件同名的文件,那么将自动重命名</param>

    /// <returns>下载文件存放的文件路径</returns>

    public string 下载文件(Uri Uri, string 存放目录)

    {

        var q = WebRequest.Create(Uri).GetResponse();

        var s = q.GetResponseStream();

        var b = new BinaryReader(s);

        var file = 生成下载文件存放路径(存放目录, Uri, q.ContentType);

        FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write);

        fs.Write(b.ReadBytes((int)q.ContentLength), 0, (int)q.ContentLength);

        fs.Close();

        b.Close();

        s.Close();

        return file;

    }

    代码很简单,就不多说了,我们来完成最后的测试:

    [TestMethod]

    public void 文件下载测试()

    {

        var d = @"C:\Users\Public\Downloads";

        //首次下载

        Assert.AreEqual(@"C:\Users\Public\Downloads\filedownload.dhtml", 下载文件(new Uri("http://www.uushare.com/filedownload?user=icesee&id=2205188"), d));

        //第二次下载,遇到同名文件,自动重命名

        Assert.AreEqual(@"C:\Users\Public\Downloads\filedownload(1).dhtml", 下载文件(new Uri("http://www.uushare.com/filedownload?user=icesee&id=2205188"), d));

        //下载一个原本是gif类型的文件

        Assert.AreEqual(@"C:\Users\Public\Downloads\2naqyw8.gif", 下载文件(new Uri("http://i38.tinypic.com/2naqyw8.jpg"), d));

    }

    结语

    相较WebClient而言,WebRequest拥有更好的可控性,在WebClient无解的时候,就尝试让WebRequest上场吧。

    下载

    范例源代码:http://www.uushare.com/user/icesee/file/2214050

    本文的XPS版本:http://www.uushare.com/user/icesee/file/2214051

      

  • 相关阅读:
    注释神器
    Q币直充-迅银渠道商(php 面向对象类)
    rsync 同步多台服务器项目目录
    wdcp 打开网页显示 Apache 2 Test Page powered by CentOS -- 来自辉哥博客
    elasticsearch批量修改,批量更新某个字段
    Ik分词器没有使用---------elasticsearch-analysis-ik 5.6.3分词问题
    spring-boot-starter-data-elasticsearch 整合elasticsearch 5.x详解
    FastJson--SerializerFeature.PrettyFormat 如何实现格式化源码查看
    由Premature end of Content-Length delimited message body因发的问题排查
    JVM的YoungGC日志查看
  • 原文地址:https://www.cnblogs.com/SkyD/p/1601440.html
Copyright © 2011-2022 走看看