作者:小馬哥
日期:
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
苦笑枯 閱讀(389)
評論(0) 編輯 收藏 所屬分類:
P2P