#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include "data.h"
#include "sha1.h"
#include "parse_metafile.h"
#include "bitfield.h"
#include "message.h"
#include "policy.h"
#include "torrent.h"
// 对定义的缓冲区的说明:
// 设置缓冲区可以避免频繁读写硬盘,从而有利于保护硬盘
// 每个缓冲区结点的大小为16KB,默认生成1024个结点,总大小为16MB
// 缓冲区以256KB为单位使用,也就是临近的16个结点为一组存放一个piece
// 下标为0~15的结点存放一个piece,16~31存放一个piece,依次类推
// 也可以处理一个piece的大小不为256KB的情况,如一个piece大小为512KB
// 为了处理的方便,所有缓冲区在程序启动时统一申请,在程序结束时释放
// 缓冲区中共有多少个Btcache结点
#define btcache_len 1024
// 以下变量定义在parse_metafile.c文件
extern char *file_name;
extern Files *files_head;
extern int file_length;
extern int piece_length;
extern int pieces_length;
extern char *pieces;
extern Bitmap *bitmap;
extern int download_piece_num;
extern Peer *peer_head;
// 指向一个16MB大小的缓冲区
Btcache *btcache_head = NULL;
// 存放待下载文件的最后一个piece
Btcache *last_piece = NULL;
int last_piece_index = 0;
int last_piece_count = 0;
int last_slice_len = 0;
// 存放文件描述符
int *fds = NULL;
int fds_len = 0;
// 存放刚刚下载到的piece的索引
// 下载到一个新的piece要向所有的peer通报
int have_piece_index[64];
// 是否进入了终端模式
int end_mode = 0;
// 为Btcache节点分配内存空间
Btcache* initialize_btcache_node()
{
Btcache *node;
node = (Btcache *)malloc(sizeof(Btcache));
if(node == NULL) {
printf("%s:%d malloc error\n",__FILE__,__LINE__);
return NULL;
}
node->buff = (unsigned char *)malloc(16*1024);
if(node->buff == NULL) {
if(node != NULL) free(node);
printf("%s:%d malloc error\n",__FILE__,__LINE__);
return NULL;
}
node->index = -1;
node->begin = -1;
node->length = -1;
node->in_use = 0;
node->read_write = -1;
node->is_full = 0;
node->is_writed = 0;
node->access_count = 0;
node->next = NULL;
return node;
}
// 创建总大小为16K*1024即16MB的缓冲区
int create_btcache()
{
int i;
Btcache *node, *last;
for(i = 0; i < btcache_len; i++) {
node = initialize_btcache_node();
if( node == NULL ) {
printf("%s:%d create_btcache error\n",__FILE__,__LINE__);
release_memory_in_btcache();
return -1;
}
if( btcache_head == NULL ) { btcache_head = node; last = node; }
else { last->next = node; last = node; }
}
int count = file_length % piece_length / (16*1024);
if(file_length % piece_length % (16*1024) != 0) count++;
last_piece_count = count;
last_slice_len = file_length % piece_length % (16*1024);
if(last_slice_len == 0) last_slice_len = 16*1024;
last_piece_index = pieces_length / 20 -1;
while(count > 0) {
node = initialize_btcache_node();
if(node == NULL) {
printf("%s:%d create_btcache error\n",__FILE__,__LINE__);
release_memory_in_btcache();
return -1;
}
if(last_piece == NULL) { last_piece = node; last = node; }
else { last->next = node; last = node; }
count--;
}
for(i = 0; i < 64; i++) {
have_piece_index[i] = -1;
}
return 0;
}
// 释放缓冲区动态分配的内存
void release_memory_in_btcache()
{
Btcache *p;
p = btcache_head;
while(p != NULL) {
btcache_head = p->next;
if(p->buff != NULL) free(p->buff);
free(p);
p = btcache_head;
}
release_last_piece();
if(fds != NULL) free(fds);
}
void release_last_piece()
{
Btcache *p = last_piece;
while(p != NULL) {
last_piece = p->next;
if(p->buff != NULL) free(p->buff);
free(p);
p = last_piece;
}
}
// 判断种子文件中待下载的文件个数
int get_files_count()
{
int count = 0;
if(is_multi_files() == 0) return 1;
Files *p = files_head;
while(p != NULL) {
count++;
p = p->next;
}
return count;
}
// 根据种子文件中的信息创建保存下载数据的文件
// 通过lseek和write两个函数来实现物理存储空间的分配
int create_files()
{
int ret, i;
char buff[1] = { 0x0 };
fds_len = get_files_count();
if(fds_len < 0) return -1;
fds = (int *)malloc(fds_len * sizeof(int));
if(fds == NULL) return -1;
if( is_multi_files() == 0 ) { // 待下载的为单文件
*fds = open(file_name,O_RDWR|O_CREAT,0777);
if(*fds < 0) { printf("%s:%d error",__FILE__,__LINE__); return -1; }
ret = lseek(*fds,file_length-1,SEEK_SET);
if(ret < 0) { printf("%s:%d error",__FILE__,__LINE__); return -1; }
ret = write(*fds,buff,1);
if(ret != 1) { printf("%s:%d error",__FILE__,__LINE__); return -1; }
} else { // 待下载的是多个文件
// 查看目录是否已创建,若没有则创建
ret = chdir(file_name);
if(ret < 0) {
ret = mkdir(file_name,0777);
if(ret < 0) { printf("%s:%d error",__FILE__,__LINE__); return -1; }
ret = chdir(file_name);
if(ret < 0) { printf("%s:%d error",__FILE__,__LINE__); return -1; }
}
Files *p = files_head;
i = 0;
while(p != NULL) {
fds[i] = open(p->path,O_RDWR|O_CREAT,0777);
if(fds[i] < 0) {printf("%s:%d error",__FILE__,__LINE__); return -1;}
ret = lseek(fds[i],p->length-1,SEEK_SET);
if(ret < 0) {printf("%s:%d error",__FILE__,__LINE__); return -1;}
ret = write(fds[i],buff,1);
if(ret != 1) {printf("%s:%d error",__FILE__,__LINE__); return -1;}
p = p->next;
i++;
} //end while
} //end else
return 0;
}
// 判断一个Btcache结点(即一个slice)中的数据要写到哪个文件的哪个位置,并写入
int write_btcache_node_to_harddisk(Btcache *node)
{
long long line_position;
Files *p;
int i;
if((node == NULL) || (fds == NULL)) return -1;
// 无论是否下载多文件,将要下载的所有数据看成一个线性字节流
// line_position指示要写入硬盘的线性位置
// piece_length为每个piece长度,它被定义在parse_metafile.c中
line_position = node->index * piece_length + node->begin;
if( is_multi_files() == 0 ) { // 如果下载的是单个文件
lseek(*fds,line_position,SEEK_SET);
write(*fds,node->buff,node->length);
return 0;
}
// 下载的是多个文件
if(files_head == NULL) {
printf("%s:%d file_head is NULL",__FILE__,__LINE__);
return -1;
}
p = files_head;
i = 0;
while(p != NULL) {
if((line_position < p->length) && (line_position+node->length < p->length)) {
// 待写入的数据属于同一个文件
lseek(fds[i],line_position,SEEK_SET);
write(fds[i],node->buff,node->length);
break;
}
else if((line_position < p->length) && (line_position+node->length >= p->length)) {
// 待写入的数据跨越了两个文件或两个以上的文件
int offset = 0; // buff内的偏移,也是已写的字节数
int left = node->length; // 剩余要写的字节数
lseek(fds[i],line_position,SEEK_SET);
write(fds[i],node->buff,p->length - line_position);
offset = p->length - line_position; // offset存放已写的字节数
left = left - (p->length - line_position); // 还需写在字节数
p = p->next; // 用于获取下一个文件的长度
i++; // 获取下一个文件描述符
while(left > 0)
if(p->length >= left) { // 当前文件的长度大于等于要写的字节数
lseek(fds[i],0,SEEK_SET);
write(fds[i],node->buff+offset,left); // 写入剩余要写的字节数
left = 0;
} else { // 当前文件的长度小于要写的字节数
lseek(fds[i],0,SEEK_SET);
write(fds[i],node->buff+offset,p->length); // 写满当前文件
offset = offset + p->length;
left = left - p->length;
i++;
p = p->next;
}
break;
} else {
// 待写入的数据不应写入当前文件
line_position = line_position - p->length;
i++;
p = p->next;
}
}
return 0;
}
// 从硬盘读出数据,存放到缓冲区中,在peer需要时发送给peer
// 该函数非常类似于write_btcache_node_