作者:小马哥
日期:
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