首先声明一下,本文并无多深的技术含量,只是记录下这两天实现的一个自动下载工具过程中遇到的一些问题和解决方法。欢迎大家讨论,另外如果您有更好的实现方式,欢迎提出宝贵意见,谢谢。
问题需求:
公司服务器上的数据库每天凌晨都会备份一次,我已经做了一个工具将备份好的数据库文件压缩,并放到一个配置好下载的文件夹下,压缩后的文件大概有1G多,并且该文件会越来越大。每天上班后我会将压缩后的文件下载到我的本地来做备份。这个时候问题出现了,我每次都是通过迅雷来下载,大概需要40分钟左右才能下载完成,这就导致工作时间服务器的带宽受到了影响。PM希望这个工作可以在凌晨时间完成,不会在白天因为下载备份文件影响到服务器,所以我打算做一个自动下载的工具来实现这个功能。
开发前的分析:
1.公司要求每天下班必须关机(防止火灾),所以我需要让电脑在凌晨的特定时刻自动开机来启动下载程序。这个很容易在网上搜索到,就是在BIOS中进行设置。
http://www.cnblogs.com/taizhouxiaoba/archive/2011/04/29/2033065.html
按照这篇文章进行设置即可。
2.如果自己写程序去做下载,对于1G的文件来说我不知道多久可以完成,对自己很没有信心啊...于是还是决定使用迅雷来进行下载(凌晨时间使用迅雷对于服务器的影响比较小,因为系统也是白天比较忙活)。搜索了下,发现迅雷已经提供了API来提供下载功能,真是谢天谢地啊!不过我也许高兴的太早了,后面会有说明...
http://blog.csdn.net/ulark/article/details/5208544
这篇文章提供了调用迅雷API的方法。
3.这个工作是偷偷来完成的,所以我需要下载完成后自动关机。开始以为迅雷已经包含这种功能了,可是仔细一看,下载完成后关机这种设置只能用于当前打开迅雷的情况下,也就是说如果我关机重启迅雷这个设置就没有了,还得再次启动迅雷来进行设置。这个肯定是太不灵活了。于是乎搜到了这篇文章
http://www.cnblogs.com/xingsoft-555/archive/2009/12/18/1627518.html
可以用C#写程序关闭计算机,这样只需要判断任务完成后执行关机代码就OK了。
这样流程出来了:
BIOS设置定时自动开机-->添加计划任务在开机后的某个时刻启动我的下载工具-->调用迅雷API进行下载-->监控下载完成并执行关机
开发过程中的问题:
1.迅雷API的问题。
使用API进行下载时会首先会启动迅雷,于是出现了那让人悲催的新建任务确认框,按照上面链接中提供的方法,发现根本找不到所说的设置。后来知道迅雷5中才有相关设置,而我使用的是迅雷7...群里的朋友建议我用win32 api来进行模拟点击任务框的下载按钮,于是,打开spy++(Visual Sudio Tools里那个),分析任务框窗体,发现找不到"立即下载"这个按钮,才知道那些按钮不是windows的标准按钮,而是迅雷开发人员自己做的,所以无法直接获取到,头大。但是突然发现如果我直接按回车键就可以确认任务,大喜,找到了模拟回车按键的代码解决问题。
[DllImport("User32.dll", EntryPoint = "FindWindow")] private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", EntryPoint = "FindWindowEx")] private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); [DllImport("user32.dll", EntryPoint = "ShowWindow", CharSet = CharSet.Auto)] public static extern int ShowWindow(IntPtr hwnd, int nCmdShow); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
//下面的代码放在调用api的CommitTask方法之后执行,CommitTask方法会弹出新建任务确认框 //窗口句柄 IntPtr hwndCalc = new IntPtr(0); //嵌套窗口句柄(spy++上显示"新建任务"窗口中还有一个内嵌窗口) IntPtr BtnWnd = new IntPtr(0); //循环,直到获得窗口句柄(因为迅雷启动时有延迟,所以使用了循环监控的方式来获得"新建任务"窗体) while (hwndCalc == IntPtr.Zero) { hwndCalc = FindWindow(null, "新建任务"); //查找迅雷新建任务的句柄 Thread.Sleep(50); } //句柄不为空 if (hwndCalc != IntPtr.Zero) { //得到内嵌窗口 BtnWnd = FindWindowEx(hwndCalc, IntPtr.Zero, "ATL:25A50700", null); //激活这个窗口,否则发送回车按键无效 ShowWindow(BtnWnd, 4); //这里之所以延迟1秒,是因为迅雷弹出"新建任务"窗口后,下载地址url不会马上显示在文本框中,如果直接发送回车键,会提示"请输入url",当时郁闷了好半天不知道为什么 Thread.Sleep(1000); //模拟回车按键 keybd_event(13, 0, 0, 0); }
图 悲催的确认框
因为我是第一次使用win32 api,所以实现方式可能比较拙劣,希望做过此类开发的朋友提出更好的方法。
2.监控下载任务完成。
在这里继续鄙视一下那悲催的迅雷API。关于监控下载任务的状态,上面的链接中已经给出了相关的方法,但是我调试了很长时间却发现无法获得任务状态,也无法获得迅雷的信息,GetInfo,GetTaskInfo两个方法只会返回null。再次google了得知该api的这些方法在迅雷5的某一个版本之后就没有用了。说实话当时查到这个结果我几乎崩溃了,费了很大的力气做到现在,却得到这么个结果。在群里发泄一番,有朋友开玩笑说你可以监视迅雷下载完成"叮"那么一声啊。这个建议给了我很大的启发,什么监视完成后的信息提示窗体啊,监视声音啊实在太麻烦了,所以我想是不是还有其他的方式可以知道这个文件是否下载完成呢...终于,上帝还是给了我灵感,不自觉的发现以前一个未下载完成的文件,顿时眼前一亮,迅雷会在下载文件的后面加一个扩展名.td作为临时文件,并且会生成一个.td.cfg的文件。聪明的你也一定会想到,对着这俩文件FileSystemWatcher一下,就可以知道文件是否下载完成了吧。
3.计划任务的执行问题
下载工具做好了,测试没有问题。接下来该就是开机启动这个工具进行下载了。这里我选择了添加windows计划任务来实现这个功能,而并没有选择添加开机启动项。原因,我这个工具每天只需要定时执行一次,并不需要每次开机启动都去运行他。
添加好计划任务后,发现win7启动后停留在了登录界面上,不登录便无法启动我的工具。在计划任务属性中倒是可以设置不登录也可执行该任务。但是在设置后发现,如果这样选择那么这个任务只会在后台进程里运行,迅雷也是在后台运行,这样就无法进行下载操作...最后没有办法,只得让win7自动登录了。具体方法见下面链接:
http://www.cnblogs.com/chinafine/archive/2010/06/18/1760376.html
这个方法有些取巧了,但是因为精力有限,实在没有办法做出让win7自动登录的程序来了...
PS:这期间我还是做了一个小程序放到计划任务里执行了,发现不登录windows,计划任务属性选择"不登录也可以执行该任务",工作执行的没有问题。只是如果要弹出迅雷来进行下载,就无法实现了,因为这样设置是不能弹出迅雷的界面来的。
总结一下:
工具花了2天的时间完成,个人觉得还是有点长了,主要因为自己对于其中的技术和遇到的问题完全没有概念,耽误了时间。而且在制作过程中,有点犯了"一条路走到底"的毛病,没有好好的去考虑问题有没有其他更加方便的解决方案。因为之前的工作都在做web程序,所以在这两天的工作中我还是学到了很多的东西,比如win32 api,迅雷下载api,windows计划任务和BIOS设置自动开机等等,让我觉得写程序虽然很伤脑筋,但确实是一个令人享受的过程。最后,还是欢迎大家提出宝贵意见,如果是您,对于这种需求是否有更好的解决方案呢?