posts - 56,  comments - 12,  trackbacks - 0

作者:小马哥

日期: 2004-6-28

 

由于 Storage 类比较简单,我直接在源码基础上进行注释。掌握 Storage ,为进一步分析 StorageWrapper 类打下基础。

 

几点说明:

1、  Storage 类封装了对磁盘文件的读和写的操作。

2、  BT 既支持单个文件的下载,也支持多个文件,包括可以有子目录。但是它并不是以文件为单位进行下载和上传的,而是以“文件片断”为单位。这可以在 BT 协议规范以及另一篇讲 BT 技术的文章中看到。所以,对于多个文件的情况,它也是当作一个拼接起来的“大文件”来处理的。例如,有文件 aaa bbb ,大小分别是 400 1000 ,那么它看作一个大小为 1400 的大文件,并以此来进行片断划分。

3、  文件在下载过程中,同时提供上传,所以是以读写方式打开的, wb+ rb+ 都指的读写方式。在下载完毕之后,改为只读方式。

4、  由于下载可能中断,所以在 Storage 初始化的时候,磁盘上可能已经存在文件的部分数据,必须检查一下文件的大小。为了便于描述,我们把完整文件的大小称为“实际长度”,把文件当前的大小成为“当前长度”。

 

 

class Storage:

 

# files 是一个二元组的列表( list ),二元组包含了文件名称和长度,例如:

[(“aaa”, 100), (“bbb”, 200)]

def __init_ _(self, files, open, exists, getsize):

 

        self.ranges = []

 

# 注意,这里是 0l ,后面的 l 表示类型是长整形,而不是 01

        total = 0l

        so_far = 0l

 

        for file, length in files:

            if length != 0:

   

               # ranges 是一个三元组列表,三元组的格式是: 在“整个”文件的起始位置、结束位置、文件名。 BT 在处理多个文件的时候,是把它们看作一个拼接起来的大文件。

                self.ranges.append((total, total + length, file))

                total += length

               

               # so_far 是实际存在的文件的总长度,好像没有起作用

                if exists(file):

                    l = getsize(file)

                    if l > length:

                        l = length

so_far += l

 

# 如果文件长度为 0 则创建一个空文件

               elif not exists(file):

                open(file, 'wb').close()

   

         # begins 是一个列表,用来保存每个文件的起始位置

        self.begins = [i[0] for i in self.ranges]

        self.total_length = total

        self.handles = {}

        self.whandles = {}

        self.tops = {}

 

        # 对于每一个文件,,,

        for file, length in files:

 

            # 如果文件已经存在

            if exists(file):

                l = getsize(file)

 

               # 如果文件长度不一致,说明还没有下载完全,则以读写( rb+ )的方式打开文件。

                if l != length:

 

               handles 是一个字典,用来保存所有被打开文件(无论是只读还是读写)的句柄

               whandles 是一个字典,用来记录对应文件是否是以写的方式打开(读写也是一种写)。

                    self.handles[file] = open(file, 'rb+')

self.whandles[file] = 1 (这里是数字 1 ,而不是字母 l

 

# 如果文件长度大于实际长度,那么应该是出错了,截断它。

                    if l > length:

                        self.handles[file].truncate(length)

 

                 如果文件长度和实际长度一致,那么下载已经完成,以只读方式打开。

                else:

self.handles[file] = open(file, 'rb')

 

 

               # tops 是一个 字典,保存对应文件的“当前长度”。

                self.tops[file] = l (这里是字母 l ,不是数字 1

 

            # 如果文件并不存在,那么以读写( w+ )的方式打开

            else:

                self.handles[file] = open(file, 'wb+')

                self.whandles[file] = 1

 

# 判断起始位置为 pos ,长度为 length 的文件片断,在 Storage 初始化之前,是否就已经存在于磁盘上了。这个函数后面分析 StoageWrapper 类的时候会再提到。

如果已经存在,那么返回 true ,否则为 false

注意:如果这个片断的部分数据已经存在于磁盘上的话,那么也返回 false

 

在分析 StorageWrapper 的时候,才发现这里分析的不对。这个函数意思应该是:

判断起始位置为 pos ,长度为 length 的文件片断,在 Storage 初始化之前,是否已经在磁盘上 分配 了空间。

例如,大小为 1024k 的文件,如果获得了 1 个片断(从 256k 512k ),那么这时候,磁盘上文件的大小是 512k (也就是分配了 512k ),尽管第 0 个片断(从 0 256k )还没有获得,但磁盘上会保留这个“空洞”。

    def was_preallocated (self, pos, length):

         for file, begin, end in self._intervals(pos, length):

            if self.tops.get(file, 0) < end:

                return False

        return True

 

# 将所有原来以 读写方式打开的文件,改成只读方式打开

    def set_readonly (self):

        # may raise IOError or OSError

         for file in self.whandles.keys():

            old = self.handles[file]

            old.flush()

            old.close()

            self.handles[file] = open(file, 'rb')

 

# 获取所有文件的总长度

    def get_total_length (self):

        return self.total_length

 

 

这个函数意思是检查 起始位置 pos ,大小为 amount 的片断实际位置在哪里?

例如,假设有两个文件, aaa bbb ,大小分别是 400 1000 ,那么 pos 300 amount 200 的文件片断属于哪个文件了?它分别属于两个文件,所以返回的是

[(“aaa”, 300, 400), (“bbb”, 0, 100)]

也就是它既包含了 aaa 文件中从 300 400 这段数据,也包含了 bbb 文件从 0 100 这段数据。

 

    def _intervals (self, pos, amount):

        r = []

 

              # stop 是这个片断的结束位置。

        stop = pos + amount

 

# 通过这个函数,可以首先定位在哪个文件中,注意,可能在多个文件中(如果某个文件过小,那么,一段数据可能跨越几个文件)

# 通过例子来解释下面这句,假设 begins = [ 100, 200, 400, 1000], pos = 250 ,那么 bisect_right(self.begins, pos) 返回的是 2 ,而 p = bisect_right(self.begins, pos) – 1 就是 1 ,这表示起始位置为 250 的文件“片断”,它至少属于第 1 个文件(从 0 开始算起),也就是起始为 200 的文件。

 

p = bisect_right(self.begins, pos) – 1

 

# r 是一个三元组的列表,三元组格式是(文件名,在该文件的起始位置,在该文件的结束位置)。

       

while p < len(self.ranges) and self.ranges[p][0] < stop:

            begin, end, file = self.ranges[p]

            r.append((file, max(pos, begin) - begin, min(end, stop) - begin))

            p += 1

 

        return r

      

# 把从 pos 开始, amount 长的数据从文件中读出来,转换成一个字符串

def read (self, pos, amount):

        r = []

         for file, pos, end in self._intervals(pos, amount):

            h = self.handles[file]

            h.seek(pos)

r.append(h.read(end - pos))

 

        # list 转换为一个字符串

        return ''.join(r)

 

# 把一段字符串写到相应的磁盘文件中。

    def write (self, pos, s):

        # might raise an IOError

        total = 0

        for file, begin, end in self._intervals(pos, len(s)):

 

            # 如果该文件并不是以写的方式打开的,那么改成读写的方式打开

            if not self.whandles.has_key(file):

                self.handles[file].close()

                self.handles[file] = open(file, 'rb+')

                self.whandles[file] = 1

h = self.handles[file]

 

# 通过 seek 函数移动文件指针,可以看出来,文件不是按照顺序来写的,因为所获取的文件片断是随机的,所以写也是随机的。

# 这里有一个疑问,假设获得了第二个文件片断,起始是 1000 ,大小是 500 ,而第一个片断还没有获得,那么文件指针要移动到 1000 处,并写 500 个字节。这时候,文件的大小应该是 1500 ,尽管前面 1000 个字节是“空洞”。那么如果,直到结束,都没有获得第一个片断,又如何检测出来了?(通过检查 total ?)

            h.seek(begin)

            h.write(s[total: total + end - begin])

            total += end - begin

 

# 关闭所有打开文件

def close (self):

        for h in self.handles.s():

            h.close()
posted on 2007-01-19 00:20 苦笑枯 阅读(381) 评论(0)  编辑  收藏 所属分类: P2P

只有注册用户登录后才能发表评论。


网站导航:
 
收藏来自互联网,仅供学习。若有侵权,请与我联系!

<2007年1月>
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910

常用链接

留言簿(2)

随笔分类(56)

随笔档案(56)

搜索

  •  

最新评论

阅读排行榜

评论排行榜