2007-07-04

 

Python练习:在线更新工具(07-04)

每天学习一点Python Cookbook,总觉得不练习还是不能掌握要领,于是想写一个小工具:
1.能根据配置文件,在线更新需要的工具
2.很多绿色软件没有提供在线更新功能(如SciTe,还有卡巴的病毒库),而只是给出了更新的地址
3.更新地址有的是固定的(如卡巴的病毒库,360等),也有是不固定的(连接地址总是体现为文件名的变化)

首先实现最简单的功能:
1.读写配置文件
2.下载有固定地址的文件 ,判断是否更新的依据是文件的大小,也就是html头中的Content-Length字段
3.对于变化地址的文件,分析其下载页面,找出需要的地址,如对于PyScripter,下载页面为http://pyscripter.googlepages.com/,而更新的地址可以写成: http://pyscripter.googlecode.com/files/PyScripterv.+zip,使用了简单的正则式.

对于配置文件,先定义一下它的格式:
本地文件名,文件大小,保存目录,下载地址,下载页地址,正则式字符串
......
使用逗号分割字段,以后好处理,对于有固定地址的文件,可以没有后两项.

为了方便处理,定义一个简单的数据结构:
class UpdateItem:
    '''
    test.zip,20,c:\temp,http://www.xxx.com,
    '''
    def __init__(self,sequence):
        self.fileName = sequence[0]
        self.fileSize = int(sequence[1])
        self.filePath = sequence[2]
        self.url = sequence[3]
        self.len = 4
        if len(sequence) > 4:
            self.pageUrl = sequence[4]
            self.regstr = sequence[5]
            self.len = 6
    def __str__(self):
        str1 = ','.join((self.fileName,str(self.fileSize),self.filePath,self.url))
        if self.len != 4:
            str1 = ','.join((str1, self.pageUrl,self.regstr))
        return str1
    def __len__(self):
        return self.len

其中的变量保存配置文件中对应的字段,构造函数中传递的sequnce列表是为了方便代码的书写.
__len__方法是为了日后判断是否有后两个字段用的.
__str__方法是为了以后写配置文件时方便,直接用str(item)就可以了.
还是用java的思路来写Python代码,所以看起来比较怪,哈哈.

关于读配置文件,我决定首先要求用写一个简单的配置文件,否则程序进行不下去啊:

if __name__ == '__main__':
    '''
    '''
    lists = []
    if os.path.exists('updater.dat '):
        f = open('updater.dat')
        try:
            for line in f:
                args = line.split(',')
                lists.append(UpdateItem(args))
          finally:
              f.close()
    else:
        print 'Make a config file first!'

这里为了方便,我们定义配置文件叫updater.dat了,用一个列表保存所有需要下载的项.

关于写配置文件,当下载完成后,需要更新数据,可以这样写:

        f = open('updater.dat','w')
        try:
            for item in lists:
                f.write(str(item))
        finally:
            f.close()
这就用到了UpdateItem的__str__方法了.

下面就是程序的关键了部分:下载,首先是主程序的接口:

        for item in lists:
            parse = UrlParse(item)
            parse.download()

我们定义一个类,UrlParse,接收下载配置项,然后提供分析和下载的功能:

class UrlParse:
    def __init__(self, item):
        self.item = item
里面保留一个item对象,方便以后处理.

在下载以前,首先要判断是否需要更新,仅仅判断html头部就可以了:

    def isNewSize(self, oldsize):
        self.socket = urllib.urlopen(self.item.url)
        fileSize = int(self.socket.headers['Content-Length'])
        if oldsize == fileSize:
            return False
        self.item.fileSize = fileSize
        return True

如果文件大小发生了变化,就认为需要更新(算法太简单,不过应该能适用于大多数情况)
下面是下载函数了,它首先要判读是否要先分析出下载地址:

    def download(self):
        if len( self.item) > 4:
            ##have to parse the page first
            self.socket = urllib.urlopen(self.item.pageUrl)
            try:
                content = self.socket.read()
                pattern = self.item.regstr
                pattern = pattern.replace(':','%3A')
                pattern = pattern.replace('/','%2F')
                match = re.search(pattern,content)
                self.item.url = match.group()[:]
                self.item.url = self.item.url.replace('%3A',':')
                self.item.url = self.item.url.replace('%2F','/')
                print 'Get a file url==>', self.item.url
            finally:
                self.socket.close()

这里用到UpdateItem的__len__方法,只要配置文件里面多于4项,就认为需要分析文件的下载地址,首先把下载页面读入content中,然后来根据正则式找出需要的地址,因为在content中,':'被替换为'%3A','/'被替换为'%2F', 所以我们首先要修正一下正则式字符串.查找完毕后,再把它改回来(好像这个方法有些笨,留下以后改).
接下来,得到了文件的下载地址,需要判断是否需要下载了:
        if not self.isNewSize(self.item.fileSize):
            print self.item.fileName, '==>Up to Date'
            return

最后的工作,就是下载了,这里我是自己写代码下载的,以后可以改成用wget下载,更快一些:

        f = open(''.join((self.item.filePath,'/',self.item.fileName)), 'wb')
        try:
            while True:
                data = self.socket.read(8192)
                if not data:
                    break
                f.write(data)
        finally:
            self.socket.close()
            f.close()

这样,简单的updater就完成了,下面是完整的代码:

import urllib,os,re
class UpdateItem:
    '''
    test.zip,20,c:\temp,http://www.xxx.com,
    '''
    def __init__(self,sequence):
        self.fileName = sequence[0]
        self.fileSize = int(sequence[1])
        self.filePath = sequence[2]
        self.url = sequence[3]
        self.len = 4
        if len(sequence) > 4:
            self.pageUrl = sequence[4]
            self.regstr = sequence[5]
            self.len = 6
    def __str__(self):
        str1 = ','.join((self.fileName,str(self.fileSize),self.filePath,self.url))
        if self.len != 4:
            str1 = ','.join((str1,self.pageUrl,self.regstr))
        return str1
    def __len__(self):
        return self.len
class UrlParse:
    def __init__(self, item):
        self.item = item
    def isNewSize(self, oldsize):
        self.socket = urllib.urlopen(self.item.url)
        fileSize = int(self.socket.headers['Content-Length'])
        if oldsize == fileSize:
            return False
        self.item.fileSize = fileSize
        return True
    def download(self):
        if len(self.item) > 4:
            ##have to parse the page first
            self.socket = urllib.urlopen(self.item.pageUrl )
            try:
                content = self.socket.read()
                pattern = self.item.regstr
                pattern = pattern.replace(':','%3A')
                pattern = pattern.replace ('/','%2F')
                match = re.search(pattern,content)
                self.item.url = match.group()[:]
                self.item.url = self.item.url.replace('%3A',':')
                self.item.url = self.item.url.replace('%2F','/')
                print 'Get a file url==>',self.item.url
            finally:
                self.socket.close()

        if not self.isNewSize(self.item.fileSize):
            print self.item.fileName, '==>Up to Date'
            return
        f = open(''.join((self.item.filePath,'/',self.item.fileName )), 'wb')
        try:
            while True:
                data = self.socket.read(8192)
                if not data:
                    break
                f.write(data)
        finally:
            self.socket.close()
            f.close()
if __name__ == '__main__':
    '''
    '''
    lists = []
    if os.path.exists('updater.dat'):
        f = open(' updater.dat')
        try:
            for line in f:
                args = line.split(',')
                lists.append(UpdateItem(args))
          finally:
              f.close()
        for item in lists:
            parse = UrlParse(item)
            parse.download()
        f = open('updater.dat','w')
        try:
            for item in lists:
                f.write(str(item))
        finally:
            f.close()
       else:
        print 'Please make a config file.'

应该改进的地方:

1.多线程,可以同时对多个文件进行判断,也可以提示更友好的信息,比如下载进度
2.下载方式,使用第三方的下载工具,这样更快
3.改进代码,让代码更易读.

Comments: 发表评论



<< Home

This page is powered by Blogger. Isn't yours?