作者:小馬哥
日期:
2004-6-28
由于
Storage
類比較簡(jiǎn)單,我直接在源碼基礎(chǔ)上進(jìn)行注釋。掌握
Storage
,為進(jìn)一步分析
StorageWrapper
類打下基礎(chǔ)。
幾點(diǎn)說(shuō)明:
1、
Storage
類封裝了對(duì)磁盤文件的讀和寫的操作。
2、
BT
既支持單個(gè)文件的下載,也支持多個(gè)文件,包括可以有子目錄。但是它并不是以文件為單位進(jìn)行下載和上傳的,而是以“文件片斷”為單位。這可以在
BT
協(xié)議規(guī)范以及另一篇講
BT
技術(shù)的文章中看到。所以,對(duì)于多個(gè)文件的情況,它也是當(dāng)作一個(gè)拼接起來(lái)的“大文件”來(lái)處理的。例如,有文件
aaa
和
bbb
,大小分別是
400
和
1000
,那么它看作一個(gè)大小為
1400
的大文件,并以此來(lái)進(jìn)行片斷劃分。
3、
文件在下載過(guò)程中,同時(shí)提供上傳,所以是以讀寫方式打開的,
wb+
和
rb+
都指的讀寫方式。在下載完畢之后,改為只讀方式。
4、
由于下載可能中斷,所以在
Storage
初始化的時(shí)候,磁盤上可能已經(jīng)存在文件的部分?jǐn)?shù)據(jù),必須檢查一下文件的大小。為了便于描述,我們把完整文件的大小稱為“實(shí)際長(zhǎng)度”,把文件當(dāng)前的大小成為“當(dāng)前長(zhǎng)度”。
class
Storage:
# files
是一個(gè)二元組的列表(
list
),二元組包含了文件名稱和長(zhǎng)度,例如:
[(“aaa”, 100), (“bbb”, 200)]
def
__init_
_(self, files, open, exists, getsize):
self.ranges = []
#
注意,這里是
0l
,后面的
l
表示類型是長(zhǎng)整形,而不是
01
。
total = 0l
so_far = 0l
for
file, length
in
files:
if
length != 0:
# ranges
是一個(gè)三元組列表,三元組的格式是:
在“整個(gè)”文件的起始位置、結(jié)束位置、文件名。
BT
在處理多個(gè)文件的時(shí)候,是把它們看作一個(gè)拼接起來(lái)的大文件。
self.ranges.append((total, total + length, file))
total += length
# so_far
是實(shí)際存在的文件的總長(zhǎng)度,好像沒有起作用
if
exists(file):
l = getsize(file)
if l > length:
l = length
so_far += l
#
如果文件長(zhǎng)度為
0
,
則創(chuàng)建一個(gè)空文件
elif not
exists(file):
open(file, 'wb').close()
# begins
是一個(gè)列表,用來(lái)保存每個(gè)文件的起始位置
self.begins = [i[0] for i in self.ranges]
self.total_length = total
self.handles = {}
self.whandles = {}
self.tops = {}
#
對(duì)于每一個(gè)文件,,,
for
file, length
in
files:
#
如果文件已經(jīng)存在
if
exists(file):
l = getsize(file)
#
如果文件長(zhǎng)度不一致,說(shuō)明還沒有下載完全,則以讀寫(
rb+
)的方式打開文件。
if
l != length:
handles
是一個(gè)字典,用來(lái)保存所有被打開文件(無(wú)論是只讀還是讀寫)的句柄
whandles
是一個(gè)字典,用來(lái)記錄對(duì)應(yīng)文件是否是以寫的方式打開(讀寫也是一種寫)。
self.handles[file] = open(file, 'rb+')
self.whandles[file] = 1
(這里是數(shù)字
1
,而不是字母
l
)
#
如果文件長(zhǎng)度大于實(shí)際長(zhǎng)度,那么應(yīng)該是出錯(cuò)了,截?cái)嗨?/span>
if l > length:
self.handles[file].truncate(length)
如果文件長(zhǎng)度和實(shí)際長(zhǎng)度一致,那么下載已經(jīng)完成,以只讀方式打開。
else:
self.handles[file] = open(file, 'rb')
# tops
是一個(gè)
字典,保存對(duì)應(yīng)文件的“當(dāng)前長(zhǎng)度”。
self.tops[file] = l
(這里是字母
l
,不是數(shù)字
1
)
#
如果文件并不存在,那么以讀寫(
w+
)的方式打開
else:
self.handles[file] = open(file, 'wb+')
self.whandles[file] = 1
#
判斷起始位置為
pos
,長(zhǎng)度為
length
的文件片斷,在
Storage
初始化之前,是否就已經(jīng)存在于磁盤上了。這個(gè)函數(shù)后面分析
StoageWrapper
類的時(shí)候會(huì)再提到。
如果已經(jīng)存在,那么返回
true
,否則為
false
。
注意:如果這個(gè)片斷的部分?jǐn)?shù)據(jù)已經(jīng)存在于磁盤上的話,那么也返回
false
。
在分析
StorageWrapper
的時(shí)候,才發(fā)現(xiàn)這里分析的不對(duì)。這個(gè)函數(shù)意思應(yīng)該是:
判斷起始位置為
pos
,長(zhǎng)度為
length
的文件片斷,在
Storage
初始化之前,是否已經(jīng)在磁盤上
分配
了空間。
例如,大小為
1024k
的文件,如果獲得了
第
1
個(gè)片斷(從
256k
到
512k
),那么這時(shí)候,磁盤上文件的大小是
512k
(也就是分配了
512k
),盡管第
0
個(gè)片斷(從
0
到
256k
)還沒有獲得,但磁盤上會(huì)保留這個(gè)“空洞”。
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
#
將所有原來(lái)以
讀寫方式打開的文件,改成只讀方式打開
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')
#
獲取所有文件的總長(zhǎng)度
def
get_total_length
(self):
return
self.total_length
這個(gè)函數(shù)意思是檢查
起始位置
為
pos
,大小為
amount
的片斷實(shí)際位置在哪里?
例如,假設(shè)有兩個(gè)文件,
aaa
和
bbb
,大小分別是
400
和
1000
,那么
pos
為
300
,
amount
為
200
的文件片斷屬于哪個(gè)文件了?它分別屬于兩個(gè)文件,所以返回的是
[(“aaa”, 300, 400), (“bbb”, 0, 100)]
,
也就是它既包含了
aaa
文件中從
300
到
400
這段數(shù)據(jù),也包含了
bbb
文件從
0
到
100
這段數(shù)據(jù)。
def
_intervals
(self, pos, amount):
r = []
# stop
是這個(gè)片斷的結(jié)束位置。
stop = pos + amount
#
通過(guò)這個(gè)函數(shù),可以首先定位在哪個(gè)文件中,注意,可能在多個(gè)文件中(如果某個(gè)文件過(guò)小,那么,一段數(shù)據(jù)可能跨越幾個(gè)文件)
#
通過(guò)例子來(lái)解釋下面這句,假設(shè)
begins = [ 100, 200, 400, 1000],
而
pos = 250
,那么
bisect_right(self.begins, pos)
返回的是
2
,而
p = bisect_right(self.begins, pos) – 1
就是
1
,這表示起始位置為
250
的文件“片斷”,它至少屬于第
1
個(gè)文件(從
0
開始算起),也就是起始為
200
的文件。
p = bisect_right(self.begins, pos) – 1
# r
是一個(gè)三元組的列表,三元組格式是(文件名,在該文件的起始位置,在該文件的結(jié)束位置)。
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
長(zhǎng)的數(shù)據(jù)從文件中讀出來(lái),轉(zhuǎn)換成一個(gè)字符串
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
轉(zhuǎn)換為一個(gè)字符串
return
''.join(r)
#
把一段字符串寫到相應(yīng)的磁盤文件中。
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]
#
通過(guò)
seek
函數(shù)移動(dòng)文件指針,可以看出來(lái),文件不是按照順序來(lái)寫的,因?yàn)樗@取的文件片斷是隨機(jī)的,所以寫也是隨機(jī)的。
#
這里有一個(gè)疑問,假設(shè)獲得了第二個(gè)文件片斷,起始是
1000
,大小是
500
,而第一個(gè)片斷還沒有獲得,那么文件指針要移動(dòng)到
1000
處,并寫
500
個(gè)字節(jié)。這時(shí)候,文件的大小應(yīng)該是
1500
,盡管前面
1000
個(gè)字節(jié)是“空洞”。那么如果,直到結(jié)束,都沒有獲得第一個(gè)片斷,又如何檢測(cè)出來(lái)了?(通過(guò)檢查
total
?)
h.seek(begin)
h.write(s[total: total + end - begin])
total += end - begin
#
關(guān)閉所有打開文件
def
close
(self):
for
h
in
self.handles.s():
h.close()
posted on 2007-01-19 00:20
苦笑枯 閱讀(394)
評(píng)論(0) 編輯 收藏 所屬分類:
P2P