+-
++void BitField::set_range(int from,int to,bool value) {
++ for(int i=from; i<to; i++)
++ set_bit(i,value);
++}
+
+ void TorrentBlackList::check_expire()
+ {
+ for(Timer *e=bl.each_begin(); e; e=bl.each_next()) {
+ if(e->Stopped()) {
+ Log::global->Format(4,"---- black-delisting peer %s\n",bl.each_key().get());
+- delete e;
+ bl.remove(bl.each_key());
+ }
+ }
+diff --git a/src/Torrent.h b/src/Torrent.h
+index a9943c6..ef5d75c 100644
+--- a/src/Torrent.h
++++ b/src/Torrent.h
+@@ -53,20 +53,105 @@ public:
+ int get_bit_length() const { return bit_length; }
+ void set_bit_length(int b) { bit_length=b; set_length((b+7)/8); }
+ void clear() { memset(buf,0,length()); }
++ void set_range(int from,int to,bool value);
+ };
+
+-struct TorrentPiece
++class TorrentPiece
+ {
+ unsigned sources_count; // how many peers have the piece
++ unsigned downloader_count; // how many downloaders of the piece are there
++ RefToArray<const TorrentPeer*> downloader; // which peers download the blocks
++ Ref<BitField> block_map; // which blocks are present.
+
+- BitField block_map; // which blocks are present
+- xarray<const TorrentPeer*> downloader; // which peers download the blocks
++public:
++ TorrentPiece() : sources_count(0), downloader_count(0) {}
++ ~TorrentPiece() {}
++
++ unsigned get_sources_count() const { return sources_count; }
++ void add_sources_count(int diff) { sources_count+=diff; }
++ bool has_no_sources() const { return sources_count==0; }
++
++ bool has_a_downloader() const { return downloader_count>0; }
++ void set_downloader(unsigned block,const TorrentPeer *o,const TorrentPeer *n,unsigned blk_count) {
++ if(!downloader) {
++ if(o || !n)
++ return;
++ downloader=new const TorrentPeer*[blk_count]();
++ }
++ const TorrentPeer*& d=downloader[block];
++ if(d==o) {
++ d=n;
++ downloader_count+=(n!=0)-(o!=0);
++ }
++ }
++ void cleanup() {
++ if(downloader_count==0 && downloader)
++ downloader=0;
++ }
++ const TorrentPeer *downloader_for(unsigned block) {
++ return downloader ? downloader[block] : 0;
++ }
+
+- TorrentPiece(unsigned b)
+- : sources_count(0), block_map(b)
+- { downloader.allocate(b,0); }
++ void set_block_present(unsigned block,unsigned blk_count) {
++ if(!block_map)
++ block_map=new BitField(blk_count);
++ block_map->set_bit(block,1);
++ }
++ void set_blocks_absent() {
++ block_map=0;
++ }
++ void free_block_map() {
++ block_map=0;
++ }
++ bool block_present(unsigned block) const {
++ return block_map && block_map->get_bit(block);
++ }
++ bool all_blocks_present(unsigned blk_count) const {
++ return block_map && block_map->has_all_set(0,blk_count);
++ }
++ bool any_blocks_present() const {
++ return block_map; // it's allocated when setting any bit
++ }
++};
++
++struct TorrentFile
++{
++ char *path;
++ off_t pos;
++ off_t length;
++ void set(const char *n,off_t p,off_t l) {
++ path=xstrdup(n);
++ pos=p;
++ length=l;
++ }
++ void unset() {
++ xfree(path); path=0;
++ }
++ bool contains_pos(off_t p) const {
++ return p>=pos && p<pos+length;
++ }
++};
+
+- bool has_a_downloader() const;
++class TorrentFiles : public xarray<TorrentFile>
++{
++ static int pos_cmp(const TorrentFile *a, const TorrentFile *b) {
++ if(a->pos < b->pos)
++ return -1;
++ if(a->pos > b->pos)
++ return 1;
++ // we want zero-sized files to placed before non-zero ones.
++ if(a->length != b->length)
++ return a->length < b->length ? -1 : 1;
++ return 0;
++ }
++public:
++ TorrentFile *file(int i) { return get_non_const()+i; }
++ TorrentFiles(const BeNode *f_node,const Torrent *t);
++ ~TorrentFiles() {
++ for(int i=0; i<length(); i++)
++ file(i)->unset();
++ }
++ TorrentFile *FindByPosition(off_t p);
+ };
+
+ class TorrentListener : public SMTask, protected ProtoLog, protected Networker
+@@ -99,6 +184,7 @@ class Torrent : public SMTask, protected ProtoLog, public ResClient
+ friend class TorrentPeer;
+ friend class TorrentDispatcher;
+ friend class TorrentListener;
++ friend class TorrentFiles;
+ friend class DHT;
+
+ bool shutting_down;
+@@ -171,6 +257,7 @@ class Torrent : public SMTask, protected ProtoLog, public ResClient
+ xstring info_hash;
+ const xstring *pieces;
+ xstring name;
++ Ref<TorrentFiles> files;
+
+ Ref<DirectedBuffer> recv_translate;
+ Ref<DirectedBuffer> recv_translate_utf8;
+@@ -214,11 +301,32 @@ class Torrent : public SMTask, protected ProtoLog, public ResClient
+ BeNode *Lookup(Ref<BeNode>& d,const char *name,BeNode::be_type_t type) { return Lookup(d->dict,name,type); }
+
+ TaskRefArray<TorrentPeer> peers;
+- RefArray<TorrentPiece> piece_info;
+ static int PeersCompareActivity(const SMTaskRef<TorrentPeer> *p1,const SMTaskRef<TorrentPeer> *p2);
+ static int PeersCompareRecvRate(const SMTaskRef<TorrentPeer> *p1,const SMTaskRef<TorrentPeer> *p2);
+ static int PeersCompareSendRate(const SMTaskRef<TorrentPeer> *p1,const SMTaskRef<TorrentPeer> *p2);
+
++ RefToArray<TorrentPiece> piece_info;
++ unsigned blocks_in_piece;
++ unsigned blocks_in_last_piece;
++ bool BlockPresent(unsigned piece,unsigned block) const {
++ return piece_info[piece].block_present(block);
++ }
++ bool AllBlocksPresent(unsigned piece) const {
++ return piece_info[piece].all_blocks_present(BlocksInPiece(piece));
++ }
++ bool AnyBlocksPresent(unsigned piece) const {
++ return piece_info[piece].any_blocks_present();
++ }
++ bool AllBlocksAbsent(unsigned piece) const {
++ return !AnyBlocksPresent(piece);
++ }
++ void SetBlocksAbsent(unsigned piece) {
++ piece_info[piece].set_blocks_absent();
++ }
++ void SetBlockPresent(unsigned piece,unsigned block) {
++ piece_info[piece].set_block_present(block,BlocksInPiece(piece));
++ }
++
+ void RebuildPiecesNeeded();
+ Timer pieces_needed_rebuild_timer;
+ xarray<unsigned> pieces_needed;
+@@ -296,6 +404,7 @@ public:
+ static void ClassInit();
+
+ Torrent(const char *mf,const char *cwd,const char *output_dir);
++ ~Torrent();
+
+ int Do();
+ int Done() const;
+@@ -315,7 +424,7 @@ public:
+ static void SHA1(const xstring& str,xstring& buf);
+ void ValidatePiece(unsigned p);
+ unsigned PieceLength(unsigned p) const { return p==total_pieces-1 ? last_piece_length : piece_length; }
+- unsigned BlocksInPiece(unsigned p) const { return (PieceLength(p)+BLOCK_SIZE-1)/BLOCK_SIZE; }
++ unsigned BlocksInPiece(unsigned p) const { return p==total_pieces-1 ? blocks_in_last_piece : blocks_in_piece; }
+
+ const TaskRefArray<TorrentPeer>& GetPeers() const { return peers; }
+ void AddPeer(TorrentPeer *);
+@@ -772,7 +881,7 @@ public:
+
+ class TorrentBlackList
+ {
+- xmap<Timer*> bl;
++ xmap_p<Timer> bl;
+ void check_expire();
+ public:
+ bool Listed(const sockaddr_u &a);
+diff --git a/src/resource.cc b/src/resource.cc
+index 4aa071a..e4707e7 100644
+--- a/src/resource.cc
++++ b/src/resource.cc
+@@ -57,13 +57,39 @@ static const char *FtpProxyValidate(xstring_c *p)
+ return 0;
+ }
+
++static const char *SetValidate(xstring_c& s,const char *const *set,const char *name)
++{
++ const char *const *scan;
++ for(scan=set; *scan; scan++)
++ if(s.eq(*scan))
++ return 0;
++
++ xstring &j=xstring::get_tmp();
++ if(name)
++ j.setf(_("%s must be one of: "),name);
++ else
++ j.set(_("must be one of: "));
++ bool had_empty=false;
++ for(scan=set; *scan; scan++) {
++ if(!**scan) {
++ had_empty=true;
++ continue;
++ }
++ if(scan>set)
++ j.append(", ");
++ j.append(*scan);
++ }
++ if(had_empty)
++ j.append(_(", or empty"));
++ return j;
++}
++
+ static const char *FtpProxyAuthTypeValidate(xstring_c *s)
+ {
+- if(s->ne("user") && s->ne("joined") && s->ne("joined-acct")
+- && s->ne("open") && s->ne("proxy-user@host"))
+- // for translator: `user', `joined', `joined-acct', `open' are literals.
+- return _("ftp:proxy-auth-type must be one of: user, joined, joined-acct, open, proxy-user@host");
+- return 0;
++ static const char *const valid_set[]={
++ "user", "joined", "joined-acct", "open", "proxy-user@host", 0
++ };
++ return SetValidate(*s,valid_set,"ftp:proxy-auth-type");
+ }
+
+ static const char *HttpProxyValidate(xstring_c *p)
+@@ -130,6 +156,14 @@ const char *OrderValidate(xstring_c *s)
+ return 0;
+ }
+
++static const char *SortByValidate(xstring_c *s)
++{
++ static const char * const valid_set[]={
++ "name", "name-desc", "size", "size-desc", "date", "date-desc", 0
++ };
++ return SetValidate(*s,valid_set,"mirror:order-by");
++}
++
+ #if USE_SSL
+ static
+ const char *AuthArgValidate(xstring_c *s)
+@@ -137,30 +171,21 @@ const char *AuthArgValidate(xstring_c *s)
+ for(char *i=s->get_non_const(); *i; i++)
+ *i=to_ascii_upper(*i);
+
+- if(strcmp(*s,"SSL")
+- && strcmp(*s,"TLS")
+- && strcmp(*s,"TLS-P")
+- && strcmp(*s,"TLS-C"))
+- return _("ftp:ssl-auth must be one of: SSL, TLS, TLS-P, TLS-C");
+-
+- return 0;
++ const char *const valid_set[]={
++ "SSL", "TLS", "TLS-P", "TLS-C", 0
++ };
++ return SetValidate(*s,valid_set,"ftp:ssl-auth");
+ }
+ static
+ const char *ProtValidate(xstring_c *s)
+ {
+- if(!**s)
+- return 0;
+-
+ for(char *i=s->get_non_const(); *i; i++)
+ *i=to_ascii_upper(*i);
+
+- if(strcmp(*s,"P")
+- && strcmp(*s,"C")
+- && strcmp(*s,"S")
+- && strcmp(*s,"E"))
+- return _("must be one of: C, S, E, P, or empty");
+-
+- return 0;
++ const char *const valid_set[]={
++ "C", "S", "E", "P", "", 0
++ };
++ return SetValidate(*s,valid_set,"ftps:initial-prot");
+ }
+ #endif
+
+@@ -300,6 +325,7 @@ static ResType lftp_vars[] = {
+ {"net:connection-limit", "0", ResMgr::UNumberValidate,0},
+ {"net:connection-takeover", "yes", ResMgr::BoolValidate,0},
+
++ {"mirror:sort-by", "name", SortByValidate,ResMgr::NoClosure},
+ {"mirror:order", "*.sfv *.sig *.md5* *.sum * */", 0,ResMgr::NoClosure},
+ {"mirror:parallel-directories", "yes", ResMgr::BoolValidate,ResMgr::NoClosure},
+ {"mirror:parallel-transfer-count", "1",ResMgr::UNumberValidate,ResMgr::NoClosure},
+diff --git a/src/xmap.cc b/src/xmap.cc
+index 4bc9a86..2276bce 100644
+--- a/src/xmap.cc
++++ b/src/xmap.cc
+@@ -136,6 +136,7 @@ void _xmap::_remove(entry **ep)
+ if(!ep || !*ep)
+ return;
+ entry *e=*ep;
++ e->key.unset();
+ *ep=e->next;
+ xfree(e);
+ entry_count--;
+diff --git a/src/xstring.h b/src/xstring.h
+index 264baf6..4a284ee 100644
+--- a/src/xstring.h
++++ b/src/xstring.h
+@@ -222,7 +222,8 @@ public:
+ static xstring& cat(const char *first,...) ATTRIBUTE_SENTINEL;
+ static xstring& join(const char *sep,int n,...);