commit b6884ac30d41606f6b3720ae2935990893dedca7
Author: Arkadiusz Miśkiewicz <ar...@maven.pl>
Date:   Tue Oct 18 15:01:20 2016 +0200

    - rel 2; new mirror -F glob option

 lftp-git.patch | 1178 +++++---------------------------------------------------
 lftp.spec      |    2 +-
 2 files changed, 95 insertions(+), 1085 deletions(-)
---
diff --git a/lftp.spec b/lftp.spec
index a984b3f..504db49 100644
--- a/lftp.spec
+++ b/lftp.spec
@@ -91,7 +91,7 @@ o arquivo FEATURES para uma lista mais detalhada.
 
 %prep
 %setup -q
-#%patch100 -p1
+%patch100 -p1
 %patch0 -p1
 %patch1 -p1
 %patch2 -p1
diff --git a/lftp-git.patch b/lftp-git.patch
index 6da8277..49404cb 100644
--- a/lftp-git.patch
+++ b/lftp-git.patch
@@ -1,1089 +1,99 @@
-diff --git a/doc/lftp.1 b/doc/lftp.1
-index d5d408f..9222795 100644
---- a/doc/lftp.1
-+++ b/doc/lftp.1
-@@ -1748,10 +1748,15 @@ is applied. It is never used if mirror:exclude-regex 
is empty.
- .BR mirror:no-empty-dirs " (boolean)"
- when true, mirror doesn't create empty directories (like \-\-no\-empty\-dirs 
option).
- .TP
-+.BR mirror:sort-by " (string)"
-+specifies order of file transfers. Valid values are: name, name-desc, size, 
size-desc,
-+date, date-desc. When the value is name or name-desc, then mirror:order 
setting also
-+affects the order or transfers.
-+.TP
- .BR mirror:order " (list of patterns)"
--specifies order of file transfers. E.g. setting this to "*.sfv *.sum" makes 
mirror to
-+specifies order of file transfers when sorting by name. E.g. setting this to 
"*.sfv *.sum" makes mirror to
- transfer files matching *.sfv first, then ones matching *.sum and then all 
other
--files. To process directories after other files, add "*/" to end of pattern 
list.
-+files. To process directories after other files, add "*/" to the end of 
pattern list.
- .TP
- .BR mirror:parallel-directories " (boolean)"
- if true, mirror will start processing of several directories in parallel
-diff --git a/po/pl.po b/po/pl.po
-index d569428..89e8b11 100644
---- a/po/pl.po
-+++ b/po/pl.po
-@@ -7,10 +7,10 @@
- #
- msgid ""
- msgstr ""
--"Project-Id-Version: lftp 4.4.16\n"
-+"Project-Id-Version: lftp 4.5.3\n"
- "Report-Msgid-Bugs-To: lftp-b...@lftp.yar.ru\n"
- "POT-Creation-Date: 2014-07-06 16:15+0400\n"
--"PO-Revision-Date: 2014-05-18 06:54+0200\n"
-+"PO-Revision-Date: 2014-07-13 14:15+0200\n"
- "Last-Translator: Jakub Bogusz <qbo...@pld-linux.org>\n"
- "Language-Team: Polish <translation-team...@lists.sourceforge.net>\n"
- "Language: pl\n"
-@@ -1152,12 +1152,10 @@ msgstr ""
- "uczyni dopełnianie nazw plików nie rozpoznającym wielkości liter.\n"
- 
- #: src/commands.cc:216
--#, fuzzy
- msgid "debug [OPTS] [<level>|off]"
--msgstr "debug [<poziom>|off] [-o <plik>]"
-+msgstr "debug [OPCJE] [<poziom>|off]"
- 
- #: src/commands.cc:217
--#, fuzzy
- msgid ""
- "Set debug level to given value or turn debug off completely.\n"
- " -o <file>  redirect debug output to the file\n"
-@@ -1165,8 +1163,11 @@ msgid ""
- " -p  show PID\n"
- " -t  show timestamps\n"
- msgstr ""
--"Ustaw poziom debugowania na daną wartość lub wyłącz zupełnie.\n"
--" -o <plik>  przekieruj wyjście debugowania do pliku.\n"
-+"Ustaw poziom diagnostyki na daną wartość lub wyłącz zupełnie.\n"
-+" -o <plik>  przekieruj wyjście diagnostyki do pliku.\n"
-+" -c  wypisuj kontekst komunikatu\n"
-+" -p  wypisuj PID\n"
-+" -t  wypisuj znaczniki czasu\n"
- 
- #: src/commands.cc:222
- msgid "du [options] <dirs>"
-@@ -1477,12 +1478,10 @@ msgstr ""
- "podano nazw obu katalogów, to będą użyte bieżące katalogi lokalny i 
zdalny.\n"
- 
- #: src/commands.cc:339
--#, fuzzy
- msgid "mkdir [OPTS] <dirs>"
--msgstr "mkdir [-p] <katalogi>"
-+msgstr "mkdir [OPCJE] <katalogi>"
- 
- #: src/commands.cc:340
--#, fuzzy
- msgid ""
- "Make remote directories\n"
- " -p  make all levels of path\n"
-@@ -1490,6 +1489,7 @@ msgid ""
- msgstr ""
- "Utwórz zdalne katalogi\n"
- " -p  tworzy wszystkie poziomy ścieżki\n"
-+" -f  ciche działanie, bez komunikatów\n"
- 
- #: src/commands.cc:343
- msgid "module name [args]"
-@@ -2378,9 +2378,9 @@ msgid "unsupported network protocol"
- msgstr "protokół nie jest obsługiwany"
- 
- #: src/ftpclass.cc:2133 src/ftpclass.cc:2228
--#, fuzzy, c-format
-+#, c-format
- msgid "Data socket error (%s) - reconnecting"
--msgstr "Błąd połączenia (%s) - powtórne łączenie"
-+msgstr "Błąd gniazda danych (%s) - powtórne łączenie"
- 
- #: src/ftpclass.cc:2161
- #, c-format
-@@ -2700,13 +2700,12 @@ msgid "peer unexpectedly closed connection after %s"
- msgstr "druga strona nieoczekiwanie zamknęła połączenie po %s"
- 
- #: src/Torrent.cc:2903 src/Torrent.cc:3020
--#, fuzzy
- msgid "peer unexpectedly closed connection"
--msgstr "druga strona nieoczekiwanie zamknęła połączenie po %s"
-+msgstr "druga strona nieoczekiwanie zamknęła połączenie"
- 
- #: src/Torrent.cc:2905 src/Torrent.cc:2906
- msgid "peer closed connection (before handshake)"
--msgstr "druga strona zamknęła połączenie (przed jego ustanowieniem)"
-+msgstr "druga strona zamknęła połączenie (przed przywitaniem)"
- 
- #: src/Torrent.cc:2909 src/Torrent.cc:3022 src/Torrent.cc:3023
- msgid "invalid peer response format"
-@@ -2732,15 +2731,15 @@ msgstr "Przyjęto połączenie od [%s]:%d"
- #: src/Torrent.cc:3552
- #, c-format
- msgid "peer sent unknown info_hash=%s in handshake"
--msgstr ""
-+msgstr "druga strona wysłała nieznane info_hash=%s przy przywitaniu"
- 
- #: src/Torrent.cc:3576
- msgid "peer handshake timeout"
--msgstr "upłynął limit czasu nawiązywania połączenia z drugą stroną"
-+msgstr "upłynął limit czasu przywitania z drugą stroną"
- 
- #: src/Torrent.cc:3588
- msgid "peer short handshake"
--msgstr "nawiązanie połączenia z drugą stroną zostało ucięte"
-+msgstr "przywitanie z drugą stroną zostało ucięte"
- 
- #: src/Torrent.cc:3590
- msgid "peer closed just accepted connection"
-diff --git a/src/FileSet.cc b/src/FileSet.cc
-index 389578a..6380a68 100644
---- a/src/FileSet.cc
-+++ b/src/FileSet.cc
-@@ -232,6 +232,21 @@ void FileSet::Sort(sort_e newsort, bool casefold, bool 
reverse)
-    }
- }
- 
-+// reverse current sort order
-+void FileSet::ReverseSort()
-+{
-+   if(!sorted) {
-+      Sort(BYNAME,false,true);
-+      return;
-+   }
-+   int i=0;
-+   int j=sorted.length()-1;
-+   while(i<j) {
-+      sorted[i]=replace_value(sorted[j],sorted[i]);
-+      ++i,--j;
-+   }
-+}
-+
- /* Remove the current sort, allowing new entries to be added.
-  * (Nothing uses this ... */
- void FileSet::Unsort()
-diff --git a/src/FileSet.h b/src/FileSet.h
-index 7c3ac2a..155536f 100644
---- a/src/FileSet.h
-+++ b/src/FileSet.h
-@@ -180,6 +180,7 @@ public:
-    void  Sort(sort_e newsort, bool casefold=false, bool reverse=false);
-    void  Unsort();
-    void        SortByPatternList(const char *list_c);
-+   void        ReverseSort();
- 
-    void        Exclude(const char *prefix,const PatternSet *x);
-    void        ExcludeDots();
-diff --git a/src/Fish.cc b/src/Fish.cc
-index d172a20..29ba4c6 100644
---- a/src/Fish.cc
-+++ b/src/Fish.cc
-@@ -98,6 +98,15 @@ int Fish::Do()
-       timeout_timer.Reset(send_buf->EventTime());
-    if(recv_buf)
-       timeout_timer.Reset(recv_buf->EventTime());
-+   if(pty_send_buf)
-+      timeout_timer.Reset(pty_send_buf->EventTime());
-+   if(pty_recv_buf)
-+      timeout_timer.Reset(pty_recv_buf->EventTime());
-+
-+   // check for timeout only if there should be connection activity.
-+   if(state!=DISCONNECTED && (state!=CONNECTED || !RespQueueIsEmpty())
-+   && mode!=CLOSED && CheckTimeout())
-+      return MOVED;
- 
-    if((state==FILE_RECV || state==FILE_SEND)
-    && rate_limit==0)
-@@ -275,14 +284,6 @@ int Fish::Do()
-    case DONE:
-       break;
-    }
--   if(m==MOVED)
--      return MOVED;
--   if(send_buf)
--      timeout_timer.Reset(send_buf->EventTime());
--   if(recv_buf)
--      timeout_timer.Reset(recv_buf->EventTime());
--   if(CheckTimeout())
--      return MOVED;
-    return m;
- }
- 
+From ebc2ba35b578cf7241852fafddd42993e2713d94 Mon Sep 17 00:00:00 2001
+From: "Alexander V. Lukyanov" <lavv...@gmail.com>
+Date: Mon, 12 Sep 2016 10:25:54 +0300
+Subject: [PATCH] MirrorJob: new option --directory (-F) to mirror top
+ directories by glob pattern.
+
+---
+ src/MirrorJob.cc | 21 +++++++++++++++------
+ src/MirrorJob.h  |  2 ++
+ 2 files changed, 17 insertions(+), 6 deletions(-)
+
 diff --git a/src/MirrorJob.cc b/src/MirrorJob.cc
-index a19ff8e..eb148f1 100644
+index 3f25941..5ba40a0 100644
 --- a/src/MirrorJob.cc
 +++ b/src/MirrorJob.cc
-@@ -589,10 +589,16 @@ void  MirrorJob::InitSets(const FileSet *source,const 
FileSet *dest)
-    if(!(flags&DELETE))
-       to_transfer->SubtractAny(to_rm_mismatched);
- 
--   to_transfer->SortByPatternList(ResMgr::Query("mirror:order",0));
--   to_transfer->CountBytes(&bytes_to_transfer);
--   if(parent_mirror)
--      parent_mirror->AddBytesToTransfer(bytes_to_transfer);
-+   const char *sort_by=ResMgr::Query("mirror:sort-by",0);
-+   bool desc=strstr(sort_by,"-desc");
-+   if(!strncmp(sort_by,"name",4))
-+      to_transfer->SortByPatternList(ResMgr::Query("mirror:order",0));
-+   else if(!strncmp(sort_by,"date",4))
-+      to_transfer->Sort(FileSet::BYDATE);
-+   else if(!strncmp(sort_by,"size",4))
-+      to_transfer->Sort(FileSet::BYSIZE,false,true);
-+   if(desc)
-+      to_transfer->ReverseSort();
- 
-    int dir_count=0;
-    to_transfer->Count(&dir_count,NULL,NULL,NULL);
-@@ -851,10 +857,16 @@ int   MirrorJob::Do()
-       stats.dirs++;
- 
-       InitSets(source_set,target_set);
-+
-+      to_transfer->CountBytes(&bytes_to_transfer);
-+      if(parent_mirror)
-+       parent_mirror->AddBytesToTransfer(bytes_to_transfer);
-+
-       
to_rm->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
-       to_rm->rewind();
-       
to_rm_mismatched->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
-       to_rm_mismatched->rewind();
-+
-       set_state(TARGET_REMOVE_OLD_FIRST);
-       goto TARGET_REMOVE_OLD_FIRST_label;
- 
-diff --git a/src/PollVec.cc b/src/PollVec.cc
-index a18b517..e1d72fb 100644
---- a/src/PollVec.cc
-+++ b/src/PollVec.cc
-@@ -31,7 +31,7 @@ static inline bool operator<(const timeval& a,const timeval& 
b)
- void PollVec::AddTimeoutU(unsigned t)
- {
-    struct timeval new_timeout={t/1000000,t%1000000};
--   if(new_timeout<tv_timeout)
-+   if(tv_timeout.tv_sec<0 || new_timeout<tv_timeout)
-       SetTimeout(new_timeout);
- }
- 
-diff --git a/src/ProcWait.cc b/src/ProcWait.cc
-index 0a065ad..d3dfec4 100644
---- a/src/ProcWait.cc
-+++ b/src/ProcWait.cc
-@@ -24,7 +24,14 @@
- #include "ProcWait.h"
- #include "SignalHook.h"
- 
--ProcWait *ProcWait::chain=0;
-+xmap<ProcWait*> ProcWait::all_proc;
-+
-+const xstring& ProcWait::proc_key(pid_t p)
-+{
-+   static xstring tmp_key;
-+   tmp_key.nset((const char*)&p,sizeof(p));
-+   return tmp_key;
-+}
- 
- int ProcWait::Do()
- {
-@@ -100,27 +107,19 @@ int ProcWait::Kill(int sig)
- }
- 
- ProcWait::ProcWait(pid_t p)
-+   : pid(p)
- {
-    auto_die=false;
--   pid=p;
-    status=RUNNING;
-    term_info=-1;
-    saved_errno=0;
- 
--   next=chain;
--   chain=this;
-+   all_proc.add(proc_key(pid),this);
- }
- 
- ProcWait::~ProcWait()
- {
--   for(ProcWait **scan=&chain; *scan; scan=&(*scan)->next)
--   {
--      if(*scan==this)
+@@ -753,7 +753,7 @@ void MirrorJob::HandleListInfoCreation(const 
FileAccessRef& session,SMTaskRef<Li
+    if(flags&RETR_SYMLINKS)
+       list_info->FollowSymlinks();
+ 
+-   list_info->SetExclude(relative_dir,exclude);
++   list_info->SetExclude(relative_dir,top_exclude?top_exclude:exclude);
+    list_info->Roll();
+ }
+ 
+@@ -1722,6 +1722,7 @@ CMD(mirror)
+       {"verbose",optional_argument,0,'v'},
+       {"newer-than",required_argument,0,'N'},
+       {"file",required_argument,0,'f'},
++      {"directory",required_argument,0,'F'},
+       {"older-than",required_argument,0,OPT_OLDER_THAN},
+       {"size-range",required_argument,0,OPT_SIZE_RANGE},
+       {"dereference",no_argument,0,'L'},
+@@ -1786,6 +1787,7 @@ CMD(mirror)
+    const char *recursion_mode=0;
+ 
+    Ref<PatternSet> exclude;
++   Ref<PatternSet> top_exclude;
+ 
+    if(!ResMgr::QueryBool("mirror:set-permissions",0))
+       flags|=MirrorJob::NO_PERMS;
+@@ -1798,7 +1800,7 @@ CMD(mirror)
+    const char *target_dir=NULL;
+ 
+    args->rewind();
+-   
while((opt=args->getopt_long("esi:x:I:X:nrpcRvN:LP:af:O:",mirror_opts,0))!=EOF)
++   
while((opt=args->getopt_long("esi:x:I:X:nrpcRvN:LP:af:F:O:",mirror_opts,0))!=EOF)
+    {
+       switch(opt)
+       {
+@@ -1875,11 +1877,17 @@ CMD(mirror)
+       case('N'):
+        newer_than=optarg;
+        break;
+-      case('f'):
 -      {
--       *scan=next;
--       return;
--      }
--   }
-+   all_proc.remove(proc_key(pid));
- }
- 
- void ProcWait::SIGCHLD_handler(int sig)
-@@ -130,16 +129,9 @@ void ProcWait::SIGCHLD_handler(int sig)
-    pid_t pp=waitpid(-1,&info,WUNTRACED|WNOHANG);
-    if(pp==-1)
-       return;
--   for(ProcWait *scan=chain; scan; scan=scan->next)
--   {
--      if(scan->pid==pp)
--      {
--       scan->handle_info(info);
--       return;
--      }
--   }
--   // no WaitProc for the pid. Probably the process died too fast,
--   // but next waitpid should take care of it.
-+   ProcWait *w=all_proc.lookup(proc_key(pp));
-+   if(w && w->handle_info(info))
-+      w->Timeout(0);
- }
- 
- void ProcWait::Signal(bool yes)
-@@ -156,6 +148,6 @@ void ProcWait::Signal(bool yes)
- void ProcWait::DeleteAll()
- {
-    Signal(false);
--   for(ProcWait *scan=chain; scan; scan=scan->next)
--      Delete(scan);
-+   for(ProcWait *w=all_proc.each_begin(); w; w=all_proc.each_next())
-+      Delete(w);
- }
-diff --git a/src/ProcWait.h b/src/ProcWait.h
-index d6f0bad..651bc21 100644
---- a/src/ProcWait.h
-+++ b/src/ProcWait.h
-@@ -23,6 +23,7 @@
- #include <sys/types.h>
- #include <signal.h>
- #include "SMTask.h"
-+#include "xmap.h"
- 
- class ProcWait : public SMTask
- {
-@@ -35,10 +36,10 @@ public:
-    };
- 
- protected:
--   static ProcWait *chain;
--   ProcWait *next;
-+   static xmap<ProcWait*> all_proc;
-+   static const xstring& proc_key(pid_t p); // make key for xmap
- 
--   pid_t pid;
-+   const pid_t pid;
-    State status;
-    int         term_info;
-    int         saved_errno;
-diff --git a/src/Ref.h b/src/Ref.h
-index 8731089..c96ee96 100644
---- a/src/Ref.h
-+++ b/src/Ref.h
-@@ -51,4 +51,22 @@ public:
- 
- template<typename T> const Ref<T> Ref<T>::null;
- 
-+template<typename T> class RefToArray : public Ref<T>
-+{
-+   RefToArray<T>(const RefToArray<T>&);  // disable cloning
-+   void operator=(const RefToArray<T>&);   // and assignment
-+
-+public:
-+   RefToArray<T>() {}
-+   RefToArray<T>(T *p) : Ref<T>(p) {}
-+   ~RefToArray<T>() { delete[] Ref<T>::ptr; Ref<T>::ptr=0; }
-+   void operator=(T *p) { delete[] Ref<T>::ptr; Ref<T>::ptr=p; }
-+   T& operator[](unsigned i) const { return Ref<T>::ptr[i]; }
-+
-+   static const RefToArray<T> null;
-+};
-+
-+template<typename T> const RefToArray<T> RefToArray<T>::null;
-+
-+
- #endif
-diff --git a/src/SFtp.cc b/src/SFtp.cc
-index 8e6d0d5..3225701 100644
---- a/src/SFtp.cc
-+++ b/src/SFtp.cc
-@@ -915,11 +915,7 @@ void SFtp::HandleExpect(Expect *e)
-        for(int i=0; i<r->GetCount(); i++)
-        {
-           const NameAttrs *a=r->GetNameAttrs(i);
--          if(!file_set)
--             file_set=new FileSet;
-           FileInfo *info=MakeFileInfo(a);
--          if(info)
--             file_set->Add(info);
-           if(mode==LIST)
-           {
-              file_buf->Put(a->name);
-@@ -941,6 +937,12 @@ void SFtp::HandleExpect(Expect *e)
-                 file_buf->Put("\n");
-              }
-           }
-+          if(info)
-+          {
-+             if(!file_set)
-+                file_set=new FileSet;
-+             file_set->Add(info);
-+          }
-        }
-        if(r->Eof())
-           goto eof;
-diff --git a/src/Torrent.cc b/src/Torrent.cc
-index ffe6433..270bbd6 100644
---- a/src/Torrent.cc
-+++ b/src/Torrent.cc
-@@ -278,6 +278,10 @@ Torrent::Torrent(const char *mf,const char *c,const char 
*od)
-    dht_announce_timer.Stop();
- }
- 
-+Torrent::~Torrent()
-+{
-+}
-+
- bool Torrent::TrackersDone() const
- {
-    if(shutting_down && shutting_down_timer.Stopped())
-@@ -318,6 +322,8 @@ void Torrent::PrepareToDie()
-       RemoveTorrent(this);
-       if(GetTorrentsCount()==0) {
-        StopListener();
-+       StopDHT();
-+       StopListenerUDP();
-        fd_cache=0;
-        black_list=0;
-       }
-@@ -348,9 +354,7 @@ double Torrent::GetRatio() const
- 
- void Torrent::SetDownloader(unsigned piece,unsigned block,const TorrentPeer 
*o,const TorrentPeer *n)
- {
--   const TorrentPeer*& downloader=piece_info[piece]->downloader[block];
--   if(downloader==o)
--      downloader=n;
-+   piece_info[piece].set_downloader(block,o,n,BlocksInPiece(piece));
- }
- 
- BeNode *Torrent::Lookup(xmap_p<BeNode>& dict,const char 
*name,BeNode::be_type_t type)
-@@ -421,25 +425,18 @@ void Torrent::ValidatePiece(unsigned p)
-        complete_pieces--;
-        my_bitfield->set_bit(p,0);
-       }
--      piece_info[p]->block_map.clear();
-+      SetBlocksAbsent(p);
-    } else {
-       LogNote(11,"piece %u ok",p);
-       if(!my_bitfield->get_bit(p)) {
-        total_left-=PieceLength(p);
-        complete_pieces++;
-        my_bitfield->set_bit(p,1);
-+       piece_info[p].free_block_map();
-       }
-    }
- }
- 
--bool TorrentPiece::has_a_downloader() const
--{
--   for(int i=0; i<downloader.count(); i++)
--      if(downloader[i])
--       return true;
--   return false;
--}
--
- template<typename T>
- static inline int cmp(T a,T b)
- {
-@@ -453,8 +450,8 @@ static inline int cmp(T a,T b)
- static Torrent *cmp_torrent;
- int Torrent::PiecesNeededCmp(const unsigned *a,const unsigned *b)
- {
--   int ra=cmp_torrent->piece_info[*a]->sources_count;
--   int rb=cmp_torrent->piece_info[*b]->sources_count;
-+   int ra=cmp_torrent->piece_info[*a].get_sources_count();
-+   int rb=cmp_torrent->piece_info[*b].get_sources_count();
-    int c=cmp(ra,rb);
-    if(c) return c;
-    return cmp(*a,*b);
-@@ -721,7 +718,7 @@ void Torrent::SetMetadata(const xstring& md)
-    }
- 
-    BeNode *b_piece_length=Lookup(info,"piece length",BeNode::BE_INT);
--   if(!b_piece_length)
-+   if(!b_piece_length || b_piece_length->num<1024 || 
b_piece_length->num>INT_MAX/4)
-       return;
-    piece_length=b_piece_length->num;
-    LogNote(4,"Piece length is %u",piece_length);
-@@ -744,7 +741,7 @@ void Torrent::SetMetadata(const xstring& md)
-    if(!files) {
-       single_file=true;
-       BeNode *length=Lookup(info,"length",BeNode::BE_INT);
--      if(!length)
-+      if(!length || length->num<0)
-        return;
-       total_length=length->num;
-    } else {
-@@ -764,9 +761,12 @@ void Torrent::SetMetadata(const xstring& md)
-           return;
-        if(!Lookup(files->list[i]->dict,"path",BeNode::BE_LIST))
-           return;
-+       if(f->num<0)
-+          return;
-        total_length+=f->num;
-       }
-    }
-+   this->files=new TorrentFiles(files,this);
-    LogNote(4,"Total length is %llu",total_length);
-    total_left=total_length;
- 
-@@ -800,16 +800,18 @@ void Torrent::SetMetadata(const xstring& md)
-    SaveMetadata();
- 
-    my_bitfield=new BitField(total_pieces);
--   for(unsigned p=0; p<total_pieces; p++)
--      piece_info.append(new TorrentPiece(BlocksInPiece(p)));
-+
-+   blocks_in_piece=(piece_length+BLOCK_SIZE-1)/BLOCK_SIZE;
-+   blocks_in_last_piece=(last_piece_length+BLOCK_SIZE-1)/BLOCK_SIZE;
-+
-+   piece_info=new TorrentPiece[total_pieces]();
- 
-    if(!force_valid) {
-       validate_index=0;
-       validating=true;
-       recv_rate.Reset();
-    } else {
--      for(unsigned i=0; i<total_pieces; i++)
--       my_bitfield->set_bit(i,1);
-+      my_bitfield->set_range(0,total_pieces,1);
-       complete_pieces=total_pieces;
-       total_left=0;
-       complete=true;
-@@ -927,7 +929,7 @@ void Torrent::CalcPiecesStats()
-    for(unsigned i=0; i<total_pieces; i++) {
-       if(my_bitfield->get_bit(i))
-        continue;
--      unsigned sc=piece_info[i]->sources_count;
-+      unsigned sc=piece_info[i].get_sources_count();
-       if(min_piece_sources>sc)
-        min_piece_sources=sc;
-       if(sc==0)
-@@ -945,12 +947,13 @@ void Torrent::RebuildPiecesNeeded()
-    bool enter_end_game=true;
-    for(unsigned i=0; i<total_pieces; i++) {
-       if(!my_bitfield->get_bit(i)) {
--       if(!piece_info[i]->has_a_downloader())
-+       if(!piece_info[i].has_a_downloader())
-           enter_end_game=false;
--       if(piece_info[i]->sources_count==0)
-+       if(piece_info[i].has_no_sources())
-           continue;
-        pieces_needed.append(i);
-       }
-+      piece_info[i].cleanup();
-    }
-    if(!end_game && enter_end_game) {
-       LogNote(1,"entering End Game mode");
-@@ -1084,6 +1087,8 @@ int Torrent::Do()
-              a.sa.sa_family=AF_INET6;
-              if(inet_pton(AF_INET6,b_ip->str,&a.in6.sin6_addr)<=0)
-                 continue;
-+             if(b_port->num<=0 || b_port->num>=0x10000)
-+                continue;
-              a.set_port(b_port->num);
-              dht_ipv6->SendPing(a);
-           } else
-@@ -1092,6 +1097,8 @@ int Torrent::Do()
-              a.sa.sa_family=AF_INET;
-              if(!inet_aton(b_ip->str,&a.in.sin_addr))
-                 continue;
-+             if(b_port->num<=0 || b_port->num>=0x10000)
-+                continue;
-              a.set_port(b_port->num);
-              dht->SendPing(a);
-           }
-@@ -1249,24 +1256,51 @@ const char *Torrent::MakePath(BeNode *p) const
- }
- const char *Torrent::FindFileByPosition(unsigned piece,unsigned begin,off_t 
*f_pos,off_t *f_tail) const
- {
--   const BeNode *files=info->lookup("files",BeNode::BE_LIST);
-    off_t target_pos=(off_t)piece*piece_length+begin;
-+   TorrentFile *file=files->FindByPosition(target_pos);
-+   if(!file)
-+      return 0;
-+
-+   *f_pos=target_pos-file->pos;
-+   *f_tail=file->length-*f_pos;
-+
-+   return file->path;
-+}
-+
-+TorrentFiles::TorrentFiles(const BeNode *files,const Torrent *t)
-+{
-    if(!files) {
--      *f_pos=target_pos;
--      *f_tail=total_length-target_pos;
--      return name;
-+      grow_space(1);
-+      set_length(1);
-+      file(0)->set(t->GetName(),0,t->TotalLength());
-    } else {
-+      int count=files->list.length();
-+      grow_space(count);
-+      set_length(count);
-       off_t scan_pos=0;
--      for(int i=0; i<files->list.length(); i++) {
--       off_t file_length=files->list[i]->lookup_int("length");
--       if(scan_pos<=target_pos && scan_pos+file_length>target_pos) {
--          *f_pos=target_pos-scan_pos;
--          *f_tail=file_length-*f_pos;
--          return MakePath(files->list[i]);
--       }
-+      for(int i=0; i<count; i++) {
-+       BeNode *node=files->list[i];
-+       off_t file_length=node->lookup_int("length");
-+       file(i)->set(t->MakePath(node),scan_pos,file_length);
-        scan_pos+=file_length;
-       }
-    }
-+   qsort(pos_cmp);
-+}
-+TorrentFile *TorrentFiles::FindByPosition(off_t pos)
-+{
-+   int i=0;
-+   int j=length()-1;
-+   while(i<=j) {
-+      // invariant: the target element is in the range [i,j]
-+      int m=(i+j)/2;
-+      if(file(m)->contains_pos(pos))
-+       return file(m);
-+      if(file(m)->pos>pos)
-+       j=m-1;
-+      else
-+       i=m+1;
-+   }
-    return 0;
- }
- 
-@@ -1485,9 +1519,9 @@ void Torrent::StoreBlock(unsigned piece,unsigned 
begin,unsigned len,const char *
-    }
- 
-    while(bc-->0) {
--      piece_info[piece]->block_map.set_bit(b++,1);
-+      SetBlockPresent(piece,b++);
-    }
--   if(piece_info[piece]->block_map.has_all_set() && 
!my_bitfield->get_bit(piece)) {
-+   if(AllBlocksPresent(piece) && !my_bitfield->get_bit(piece)) {
-       ValidatePiece(piece);
-       if(!my_bitfield->get_bit(piece)) {
-        LogError(0,"new piece %u digest mismatch",piece);
-@@ -2021,12 +2055,12 @@ int TorrentPeer::SendDataRequests(unsigned p)
-    unsigned blocks=parent->BlocksInPiece(p);
-    unsigned bytes_allowed=BytesAllowed(RateLimit::GET);
-    for(unsigned b=0; b<blocks; b++) {
--      if(parent->piece_info[p]->block_map.get_bit(b))
-+      if(parent->BlockPresent(p,b))
-        continue;
--      if(parent->piece_info[p]->downloader[b]) {
-+      if(parent->piece_info[p].downloader_for(b)) {
-        if(!parent->end_game)
-           continue;
--       if(parent->piece_info[p]->downloader[b]==this)
-+       if(parent->piece_info[p].downloader_for(b)==this)
-           continue;
-        if(FindRequest(p,b*Torrent::BLOCK_SIZE)>=0)
-           continue;
-@@ -2118,8 +2152,7 @@ void TorrentPeer::SendDataRequests()
-        if(parent->my_bitfield->get_bit(p))
-           continue;
-        // add some randomness, so that different instances don't synchronize
--       if(!parent->piece_info[p]->block_map.has_any_set()
--       && random()/13%16==0)
-+       if(parent->AllBlocksAbsent(p) && random()/13%16==0)
-           continue;
-        if(SendDataRequests(p)>0)
-           return;
-@@ -2233,7 +2266,7 @@ unsigned TorrentPeer::GetLastPiece() const
-    unsigned p=last_piece;
-    // continue if have any blocks already
-    if(p!=NO_PIECE && !parent->my_bitfield->get_bit(p)
--   && parent->piece_info[p]->block_map.has_any_set()
-+   && parent->AnyBlocksPresent(p)
-    && peer_bitfield->get_bit(p))
-       return p;
-    p=parent->last_piece;
-@@ -2302,11 +2335,11 @@ void TorrentPeer::SetPieceHaving(unsigned p,bool have)
-    int diff = (have - peer_bitfield->get_bit(p));
-    if(!diff)
-       return;
--   parent->piece_info[p]->sources_count+=diff;
-+   parent->piece_info[p].add_sources_count(diff);
-    peer_complete_pieces+=diff;
-    peer_bitfield->set_bit(p,have);
- 
--   if(parent->piece_info[p]->sources_count==0)
-+   if(parent->piece_info[p].get_sources_count()==0)
-       parent->SetPieceNotWanted(p);
-    if(have && send_buf && !am_interested && !parent->my_bitfield->get_bit(p)
-    && parent->NeedMoreUploaders()) {
-@@ -2641,7 +2674,7 @@ void TorrentPeer::HandleExtendedMessage(PacketExtended 
*pp)
-        return;
-       }
-       BeNode *piece=pp->data->lookup("piece",BeNode::BE_INT);
--      if(!piece) {
-+      if(!piece || piece->num<0 || piece->num>=INT_MAX/Torrent::BLOCK_SIZE) {
-        SetError("ut_metadata piece bad or missing");
-        return;
-       }
-@@ -2692,6 +2725,9 @@ void TorrentPeer::HandleExtendedMessage(PacketExtended 
*pp)
-        }
-        case UT_METADATA_REJECT:
-           break;
-+       default:
-+          SetError("ut_metadata msg_type invalid value");
-+          return;
-       }
-    } else if(pp->code==MSG_EXT_PEX) {
-       if(!pex.recv_timer.Stopped())
-@@ -3322,14 +3358,16 @@ bool BitField::has_all_set(int from,int to) const {
-        return false;
-    return true;
- }
--
-+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,...);
+-       // mirror for a single file (or glob pattern).
++      case('f'): // mirror for a single file (or glob pattern).
+        recursion_mode="never";
+-       MirrorJob::AddPattern(exclude,'I',basename_ptr(optarg));
++       /*fallthrough*/
++      case('F'): // mirror for a single directory (or glob pattern).
++      {
++       xstring pattern(basename_ptr(optarg));
++       if(opt=='F' && pattern.last_char()!='/')
++          pattern.append('/');
++       if(!top_exclude)
++          top_exclude=new PatternSet();
++       top_exclude->Add(PatternSet::INCLUDE,new PatternSet::Glob(pattern));
+        source_dir=dirname(optarg);
+        source_dir=alloca_strdup(source_dir); // save the temp string
+        break;
+@@ -2106,6 +2114,7 @@ CMD(mirror)
+    j->SetFlags(flags,1);
+    j->SetVerbose(verbose);
+    j->SetExclude(exclude.borrow());
++   j->SetTopExclude(top_exclude.borrow());
+ 
+    if(newer_than)
+       j->SetNewerThan(newer_than);
+diff --git a/src/MirrorJob.h b/src/MirrorJob.h
+index a3da287..a9a0df8 100644
+--- a/src/MirrorJob.h
++++ b/src/MirrorJob.h
+@@ -138,6 +138,7 @@ class MirrorJob : public Job
+    recursion_mode_t recursion_mode;
+    int         max_error_count;
+ 
++   Ref<PatternSet> top_exclude;
+    Ref<PatternSet> my_exclude;
+    const PatternSet *exclude;
+ 
+@@ -249,6 +250,7 @@ class MirrorJob : public Job
+    void        SetExclude(const PatternSet *x) { exclude=x; }
+    void        SetSizeRange(Range *r) { my_size_range=r; 
size_range=my_size_range; }
+    void        SetSizeRange(const Range *r) { size_range=r; }
++   void        SetTopExclude(PatternSet *x) { top_exclude=x; }
+ 
+    void        SetVerbose(int v) { verbose_report=v; }
  
--   void truncate(size_t n=0);
-+   void truncate() { set_length(0); }
-+   void truncate(size_t n);
-    void truncate_at(char c);
-    /* set_length can be used to extend the string, e.g. after modification
-       with get_space+get_non_const. */
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/lftp.git/commitdiff/b6884ac30d41606f6b3720ae2935990893dedca7


_______________________________________________
pld-cvs-commit mailing list
pld-cvs-commit@lists.pld-linux.org
http://lists.pld-linux.org/mailman/listinfo/pld-cvs-commit

Reply via email to