ed2c4d591079c2faa26fa09977594b220a3c379e
[platform/upstream/libzypp.git] / zypp / Fetcher.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/Fetcher.cc
10  *
11 */
12 #include <iostream>
13 #include <fstream>
14 #include <list>
15 #include <map>
16
17 #include "zypp/base/Easy.h"
18 #include "zypp/base/Logger.h"
19 #include "zypp/base/PtrTypes.h"
20 #include "zypp/base/DefaultIntegral.h"
21 #include "zypp/base/String.h"
22 #include "zypp/Fetcher.h"
23 #include "zypp/CheckSum.h"
24 #include "zypp/base/UserRequestException.h"
25 #include "zypp/parser/susetags/ContentFileReader.h"
26 #include "zypp/parser/susetags/RepoIndex.h"
27
28 using namespace std;
29
30 ///////////////////////////////////////////////////////////////////
31 namespace zypp
32 { /////////////////////////////////////////////////////////////////
33
34   /**
35    * class that represents indexes which add metadata
36    * to fetcher jobs and therefore need to be retrieved
37    * in advance.
38    */
39   struct FetcherIndex
40   {
41     FetcherIndex( const OnMediaLocation &loc )
42       : location(loc)
43     {
44     }
45       
46     OnMediaLocation location;
47   };
48   typedef shared_ptr<FetcherIndex> FetcherIndex_Ptr;
49       
50   /**
51    * Class to encapsulate the \ref OnMediaLocation object
52    * and the \ref FileChecker together
53    */
54   struct FetcherJob
55   {
56     enum Flag
57     {
58         None = 0x0000,
59         Directory = 0x0001,
60         Recursive = 0x0002,
61         RecursiveDirectory = Directory | Recursive,
62         // check checksums even if there is no such
63         // checksum (warns of no checksum)
64         AlwaysVerifyChecksum = 0x0004,
65     };
66     ZYPP_DECLARE_FLAGS(Flags, Flag);
67         
68
69     FetcherJob( const OnMediaLocation &loc )
70       : location(loc)
71       , flags(None)
72     {
73       //MIL << location << endl;
74     }
75     
76     ~FetcherJob()
77     {
78       //MIL << location << " | * " << checkers.size() << endl;
79     }
80
81     OnMediaLocation location;
82     //CompositeFileChecker checkers;
83     list<FileChecker> checkers;
84     Flags flags;
85   };
86     ZYPP_DECLARE_OPERATORS_FOR_FLAGS(FetcherJob::Flags);
87   typedef shared_ptr<FetcherJob> FetcherJob_Ptr;
88
89   std::ostream & operator<<( std::ostream & str, const FetcherJob_Ptr & obj )
90   {
91     return str << obj->location;
92   }
93
94
95   ///////////////////////////////////////////////////////////////////
96   //
97   //    CLASS NAME : Fetcher::Impl
98   //
99   /** Fetcher implementation. */
100   class Fetcher::Impl
101   {
102     friend std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj );
103   public:
104       Impl();
105       
106     ~Impl() {
107       MIL << endl;
108      }
109       
110       void setOptions( Fetcher::Options options );
111       Fetcher::Options options() const;
112
113       void addIndex( const OnMediaLocation &resource );
114
115       void enqueueDir( const OnMediaLocation &resource, bool recursive, const FileChecker &checker = FileChecker() );
116       void enqueueDigestedDir( const OnMediaLocation &resource, bool recursive, const FileChecker &checker = FileChecker() );
117
118       void enqueue( const OnMediaLocation &resource, const FileChecker &checker = FileChecker()  );
119       void enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker = FileChecker() );
120     void addCachePath( const Pathname &cache_dir );
121     void reset();
122     void start( const Pathname &dest_dir,
123                 MediaSetAccess &media,
124                 const ProgressData::ReceiverFnc & progress_receiver );
125
126     /** Offer default Impl. */
127     static shared_ptr<Impl> nullimpl()
128     {
129       static shared_ptr<Impl> _nullimpl( new Impl );
130       return _nullimpl;
131     }
132   private:
133       /**
134        * download the indexes and reads them
135        */
136       void readIndexes( MediaSetAccess &media, const Pathname &dest_dir);
137
138       /**
139        * reads a downloaded index file and updates internal
140        * attributes table
141        *
142        * The index lists files relative to a directory, which is
143        * normally the same as the index file is located.
144        */
145       void readIndex( const Pathname &index, const Pathname &basedir );
146
147       /** specific version of \ref readIndex for SHA1SUMS file */
148       void readSha1sumsIndex( const Pathname &index, const Pathname &basedir );
149       
150       /** specific version of \ref readIndex for SHA1SUMS file */
151       void readContentFileIndex( const Pathname &index, const Pathname &basedir );
152       
153       /**
154        * tries to provide the file represented by job into dest_dir by
155        * looking at the cache. If success, returns true, and the desired
156        * file should be available on dest_dir
157        */
158       bool provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir );
159       /**
160        * Validates the job against is checkers, by using the file instance
161        * on dest_dir
162        * \throws Exception
163        */
164       void validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers );
165
166       /**
167        * scan the directory and adds the individual jobs
168        */
169        void addDirJobs( MediaSetAccess &media, const OnMediaLocation &resource,
170                         const Pathname &dest_dir, FetcherJob::Flags flags );
171
172       /**
173        * auto discovery and reading of indexes
174        */
175       void autoaddIndexes( const filesystem::DirContent &content,
176                            MediaSetAccess &media,
177                            const OnMediaLocation &resource,
178                            const Pathname &dest_dir );
179       /**
180        * Provide the resource to \ref dest_dir
181        */
182       void provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir );
183
184   private:
185     friend Impl * rwcowClone<Impl>( const Impl * rhs );
186     /** clone for RWCOW_pointer */
187     Impl * clone() const
188     { return new Impl( *this ); }
189
190     list<FetcherJob_Ptr> _resources;
191     list<FetcherIndex_Ptr> _indexes;
192     list<Pathname> _caches;
193     // checksums read from the indexes
194     map<string, CheckSum> _checksums;
195     Fetcher::Options _options;      
196   };
197   ///////////////////////////////////////////////////////////////////
198
199   void Fetcher::Impl::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
200   {
201     FetcherJob_Ptr job;
202     job.reset(new FetcherJob(resource));
203     job->flags |= FetcherJob:: AlwaysVerifyChecksum;
204     _resources.push_back(job);
205   }
206    
207   Fetcher::Impl::Impl()
208       : _options(0)
209   {
210   }
211     
212   void Fetcher::Impl::setOptions( Fetcher::Options options )
213   { _options = options; }
214     
215   Fetcher::Options Fetcher::Impl::options() const
216   { return _options; }
217     
218   void Fetcher::Impl::enqueueDir( const OnMediaLocation &resource,
219                                   bool recursive,
220                                   const FileChecker &checker )
221   {
222     FetcherJob_Ptr job;
223     job.reset(new FetcherJob(resource));
224     if ( checker )
225         job->checkers.push_back(checker);
226     if ( recursive )
227         job->flags |= FetcherJob::Recursive;
228     job->flags |= FetcherJob::Directory;
229
230     _resources.push_back(job);
231   }
232
233   void Fetcher::Impl::enqueueDigestedDir( const OnMediaLocation &resource,
234                                           bool recursive,
235                                           const FileChecker &checker )
236   {
237     FetcherJob_Ptr job;
238     job.reset(new FetcherJob(resource));
239     if ( checker )
240         job->checkers.push_back(checker);
241     if ( recursive )
242         job->flags |= FetcherJob::Recursive;
243     job->flags |= FetcherJob::Directory;
244     job->flags |= FetcherJob::AlwaysVerifyChecksum;
245
246     _resources.push_back(job);
247
248   }
249
250   void Fetcher::Impl::enqueue( const OnMediaLocation &resource, const FileChecker &checker )
251   {
252     FetcherJob_Ptr job;
253     job.reset(new FetcherJob(resource));
254     if ( checker )
255       job->checkers.push_back(checker);
256     _resources.push_back(job);
257   }
258
259   void Fetcher::Impl::addIndex( const OnMediaLocation &resource )
260   {
261       MIL << "adding index " << resource << endl;
262       FetcherIndex_Ptr index;
263       index.reset(new FetcherIndex(resource));
264       _indexes.push_back(index);
265   }
266
267
268   void Fetcher::Impl::reset()
269   {
270     _resources.clear();
271     _indexes.clear();
272   }
273
274   void Fetcher::Impl::addCachePath( const Pathname &cache_dir )
275   {
276     PathInfo info(cache_dir);
277     if ( info.isExist() )
278     {
279       if ( info.isDir() )
280       {
281         DBG << "Adding fetcher cache: '" << cache_dir << "'." << endl;
282         _caches.push_back(cache_dir);
283       }
284       else
285       {
286         // don't add bad cache directory, just log the error
287         ERR << "Not adding cache: '" << cache_dir << "'. Not a directory." << endl;
288       }
289     }
290     else
291     {
292         ERR << "Not adding cache '" << cache_dir << "'. Path does not exists." << endl;
293     }
294
295   }
296
297   // tries to provide resource to dest_dir from any of the configured additional
298   // cache paths where the file may already be present. returns true if the
299   // file was provided from the cache.
300   bool Fetcher::Impl::provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir )
301   {
302     Pathname dest_full_path = dest_dir + resource.filename();
303
304     // first check in the destination directory
305     if ( PathInfo(dest_full_path).isExist() )
306     {
307       if ( is_checksum( dest_full_path, resource.checksum() )
308            && (! resource.checksum().empty() ) )
309           return true;
310     }
311
312     MIL << "start fetcher with " << _caches.size() << " cache directories." << endl;
313     for_ ( it_cache, _caches.begin(), _caches.end() )
314     {
315       // does the current file exists in the current cache?
316       Pathname cached_file = *it_cache + resource.filename();
317       if ( PathInfo( cached_file ).isExist() )
318       {
319         DBG << "File '" << cached_file << "' exist, testing checksum " << resource.checksum() << endl;
320          // check the checksum
321         if ( is_checksum( cached_file, resource.checksum() ) && (! resource.checksum().empty() ) )
322         {
323           // cached
324           MIL << "file " << resource.filename() << " found in previous cache. Using cached copy." << endl;
325           // checksum is already checked.
326           // we could later implement double failover and try to download if file copy fails.
327            // replicate the complete path in the target directory
328           if( dest_full_path != cached_file )
329           {
330             if ( assert_dir( dest_full_path.dirname() ) != 0 )
331               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
332
333             if ( filesystem::hardlink(cached_file, dest_full_path ) != 0 )
334             {
335               WAR << "Can't hardlink '" << cached_file << "' to '" << dest_dir << "'. Trying copying." << endl;
336               if ( filesystem::copy(cached_file, dest_full_path ) != 0 )
337               {
338                 ERR << "Can't copy " << cached_file + " to " + dest_dir << endl;
339                 // try next cache
340                 continue;
341               }
342             }
343           }
344           // found in cache
345           return true;
346         }
347       }
348     } // iterate over caches
349     return false;
350   }
351     
352     void Fetcher::Impl::validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers )
353   {
354     // no matter where did we got the file, try to validate it:
355     Pathname localfile = dest_dir + resource.filename();
356     // call the checker function
357     try
358     {
359       MIL << "Checking job [" << localfile << "] (" << checkers.size() << " checkers )" << endl;
360
361       for ( list<FileChecker>::const_iterator it = checkers.begin();
362             it != checkers.end();
363             ++it )
364       {
365         if (*it)
366         {
367           (*it)(localfile);
368         }
369         else
370         {
371           ERR << "Invalid checker for '" << localfile << "'" << endl;
372         }
373       }
374
375     }
376     catch ( const FileCheckException &e )
377     {
378       ZYPP_RETHROW(e);
379     }
380     catch ( const Exception &e )
381     {
382       ZYPP_RETHROW(e);
383     }
384     catch (...)
385     {
386       ZYPP_THROW(Exception("Unknown error while validating " + resource.filename().asString()));
387     }
388   }
389
390   void Fetcher::Impl::autoaddIndexes( const filesystem::DirContent &content,
391                                       MediaSetAccess &media,
392                                       const OnMediaLocation &resource,
393                                       const Pathname &dest_dir )
394   {
395       if ( _options & AutoAddSha1sumsIndexes )
396       {
397           // only try to add an index if it exists
398           filesystem::DirEntry shafile;
399           shafile.name = "SHA1SUMS"; shafile.type = filesystem::FT_FILE;
400           if ( find( content.begin(), content.end(), shafile ) != content.end() )
401           {              
402               // add the index of this directory
403               OnMediaLocation indexloc(resource);
404               indexloc.changeFilename(resource.filename() + "SHA1SUMS");
405               addIndex(indexloc);
406               // we need to read it now
407               readIndexes(media, dest_dir);
408           }
409       }
410       if ( _options & AutoAddContentFileIndexes )
411       {
412           // only try to add an index if it exists
413           filesystem::DirEntry contentfile;
414           contentfile.name = "content"; contentfile.type = filesystem::FT_FILE;
415           if ( find( content.begin(), content.end(), contentfile ) != content.end() )
416           {              
417               // add the index of this directory
418               OnMediaLocation indexloc(resource);
419               indexloc.changeFilename(resource.filename() + "content");
420               addIndex(indexloc);
421               // we need to read it now
422               readIndexes(media, dest_dir);
423           }
424       }
425   }
426     
427   void Fetcher::Impl::addDirJobs( MediaSetAccess &media,
428                                   const OnMediaLocation &resource,
429                                   const Pathname &dest_dir, FetcherJob::Flags flags  )
430   {
431       // first get the content of the directory so we can add
432       // individual transfer jobs
433       MIL << "Adding directory " << resource.filename() << endl;
434       filesystem::DirContent content;
435       media.dirInfo( content, resource.filename(), false /* dots */, resource.medianr());
436       
437       // this method test for the option flags so indexes are added
438       // only if the options are enabled
439       autoaddIndexes(content, media, resource, dest_dir);
440       
441       for ( filesystem::DirContent::const_iterator it = content.begin();
442             it != content.end();
443             ++it )
444       {
445           // skip SHA1SUMS* as they were already retrieved
446           if ( str::hasPrefix(it->name, "SHA1SUMS") )
447               continue;
448
449           Pathname filename = resource.filename() + it->name;
450
451           switch ( it->type )
452           {
453           case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
454           case filesystem::FT_FILE:
455           {
456               CheckSum chksm(resource.checksum());              
457               if ( _checksums.find(filename.asString()) != _checksums.end() )
458               {
459                   // the checksum can be replaced with the one in the index.
460                   chksm = _checksums[filename.asString()];
461                   //MIL << "resource " << filename << " has checksum in the index file." << endl;
462               }
463               else
464                   WAR << "Resource " << filename << " has no checksum in the index either." << endl;
465               if ( flags & FetcherJob::AlwaysVerifyChecksum )
466                   enqueueDigested(OnMediaLocation(filename, resource.medianr()).setChecksum(chksm));
467               else
468                   enqueue(OnMediaLocation(filename, resource.medianr()).setChecksum(chksm));
469               break;
470           }
471           case filesystem::FT_DIR: // newer directory.yast contain at least directory info
472               if ( flags & FetcherJob::Recursive )
473                   addDirJobs(media, filename, dest_dir, flags);
474               break;
475           default:
476               // don't provide devices, sockets, etc.
477               break;
478           }
479       }
480   }
481
482   void Fetcher::Impl::provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir )
483   {
484     bool got_from_cache = false;
485
486     // start look in cache
487     got_from_cache = provideFromCache(resource, dest_dir);
488
489     if ( ! got_from_cache )
490     {
491       MIL << "Not found in cache, downloading" << endl;
492
493       // try to get the file from the net
494       try
495       {
496         Pathname tmp_file = media.provideFile(resource);
497         Pathname dest_full_path = dest_dir + resource.filename();
498         if ( assert_dir( dest_full_path.dirname() ) != 0 )
499               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
500         if ( filesystem::copy(tmp_file, dest_full_path ) != 0 )
501         {
502           ZYPP_THROW( Exception("Can't copy " + tmp_file.asString() + " to " + dest_dir.asString()));
503         }
504
505         media.releaseFile(resource); //not needed anymore, only eat space
506       }
507       catch (Exception & excpt_r)
508       {
509         ZYPP_CAUGHT(excpt_r);
510         excpt_r.remember("Can't provide " + resource.filename().asString() + " : " + excpt_r.msg());
511         ZYPP_RETHROW(excpt_r);
512       }
513     }
514     else
515     {
516       // We got the file from cache
517       // continue with next file
518         return;
519     }
520   }
521
522   // helper class to consume a content file
523   struct ContentReaderHelper : public parser::susetags::ContentFileReader
524   {
525     ContentReaderHelper()
526     {
527       setRepoIndexConsumer( bind( &ContentReaderHelper::consumeIndex, this, _1 ) );
528     }
529         
530     void consumeIndex( const parser::susetags::RepoIndex_Ptr & data_r )
531     { _repoindex = data_r; }
532
533     parser::susetags::RepoIndex_Ptr _repoindex;
534   };
535
536   // generic function for reading indexes
537   void Fetcher::Impl::readIndex( const Pathname &index, const Pathname &basedir )
538   {
539     if ( index.basename() == "SHA1SUMS" )
540       readSha1sumsIndex(index, basedir);
541     else if ( index.basename() == "content" )
542       readContentFileIndex(index, basedir);
543     else
544       WAR << index << ": index file format not known" << endl;
545   }
546
547   // reads a content file index
548   void Fetcher::Impl::readContentFileIndex( const Pathname &index, const Pathname &basedir )
549   {
550       ContentReaderHelper reader;
551       reader.parse(index);
552       MIL << index << " contains " << reader._repoindex->mediaFileChecksums.size() << " checksums." << endl;
553       for_( it, reader._repoindex->mediaFileChecksums.begin(), reader._repoindex->mediaFileChecksums.end() )
554       {
555           // content file entries don't start with /
556           _checksums[(basedir + it->first).asString()] = it->second;
557       }
558   }
559
560   // reads a SHA1SUMS file index
561   void Fetcher::Impl::readSha1sumsIndex( const Pathname &index, const Pathname &basedir )
562   {
563       std::ifstream in( index.c_str() );
564       string buffer;
565       if ( ! in.fail() )
566       {
567           while ( getline(in, buffer) )
568           {
569               vector<string> words;
570               str::split( buffer, back_inserter(words) );
571               if ( words.size() != 2 )
572                   ZYPP_THROW(Exception("Wrong format for SHA1SUMS file"));
573               //MIL << "check: '" << words[0] << "' | '" << words[1] << "'" << endl;
574               if ( ! words[1].empty() )
575                   _checksums[(basedir + words[1]).asString()] = CheckSum::sha1(words[0]);
576           }
577       }
578       else
579           ZYPP_THROW(Exception("Can't open SHA1SUMS file: " + index.asString()));
580   }
581     
582   // this method takes all the user pointed indexes, gets them and also tries to
583   // download their signature, and verify them. After that, its parses each one
584   // to fill the checksum cache.
585   void Fetcher::Impl::readIndexes( MediaSetAccess &media, const Pathname &dest_dir)
586   {
587       // if there is no indexes, then just return to avoid
588       // the directory listing
589       if ( _indexes.empty() )
590       {
591           MIL << "No indexes to read." << endl;
592           return;
593       }
594
595       // create a new fetcher with a different state to transfer the
596       // file containing checksums and its signature
597       Fetcher fetcher;
598       // signature checker for index. We havent got the signature from
599       // the nextwork yet.
600       SignatureFileChecker sigchecker;
601       
602       for ( list<FetcherIndex_Ptr>::const_iterator it_idx = _indexes.begin();
603             it_idx != _indexes.end(); ++it_idx )
604       {
605           MIL << "reading index " << (*it_idx)->location << endl;
606           // build the name of the index and the signature
607           OnMediaLocation idxloc((*it_idx)->location);
608           OnMediaLocation sigloc((*it_idx)->location.setOptional(true));
609           OnMediaLocation keyloc((*it_idx)->location.setOptional(true));
610
611           // calculate signature and key name
612           sigloc.changeFilename( sigloc.filename().extend(".asc") );
613           keyloc.changeFilename( keyloc.filename().extend(".key") );
614
615           //assert_dir(dest_dir + idxloc.filename().dirname());
616
617           // transfer the signature
618           fetcher.enqueue(sigloc);
619           fetcher.start( dest_dir, media );
620           // if we get the signature, update the checker
621           sigchecker = SignatureFileChecker(dest_dir + sigloc.filename());
622           fetcher.reset();
623           
624           // now the key
625           fetcher.enqueue(keyloc);
626           fetcher.start( dest_dir, media );
627           fetcher.reset();
628
629           // now the index itself
630           fetcher.enqueue( idxloc, FileChecker(sigchecker) );
631           fetcher.start( dest_dir, media );
632           fetcher.reset();
633
634           // now we have the indexes in dest_dir
635           readIndex( dest_dir + idxloc.filename(), idxloc.filename().dirname() );
636       }
637       MIL << "done reading indexes" << endl;
638   }
639     
640   // start processing all fetcher jobs.
641   // it processes any user pointed index first
642   void Fetcher::Impl::start( const Pathname &dest_dir,
643                              MediaSetAccess &media,
644                              const ProgressData::ReceiverFnc & progress_receiver )
645   {
646     ProgressData progress(_resources.size());
647     progress.sendTo(progress_receiver);
648
649     readIndexes(media, dest_dir);
650
651     for ( list<FetcherJob_Ptr>::const_iterator it_res = _resources.begin(); it_res != _resources.end(); ++it_res )
652     {
653
654       if ( (*it_res)->flags & FetcherJob::Directory )
655       {
656           const OnMediaLocation location((*it_res)->location);
657           addDirJobs(media, location, dest_dir, (*it_res)->flags);
658           continue;
659       }
660
661       provideToDest(media, (*it_res)->location, dest_dir);
662
663       // if the checksum is empty, but the checksum is in one of the
664       // indexes checksum, then add a checker
665       if ( (*it_res)->location.checksum().empty() )
666       {
667           if ( _checksums.find((*it_res)->location.filename().asString()) 
668                != _checksums.end() )
669           {
670               CheckSum chksm = _checksums[(*it_res)->location.filename().asString()];
671               ChecksumFileChecker digest_check(chksm);    
672               (*it_res)->checkers.push_back(digest_check);
673           }
674           else
675           {
676               // if the index checksum is empty too, we only add the checker
677               // if the  AlwaysVerifyChecksum option is set on
678               if ( (*it_res)->flags & FetcherJob::AlwaysVerifyChecksum ) 
679               {
680                   // add the checker with the empty checksum
681                   ChecksumFileChecker digest_check((*it_res)->location.checksum());    
682                   (*it_res)->checkers.push_back(digest_check);
683               }
684           }
685       }
686       else
687       {
688           // checksum is not empty, so add a checksum checker
689           ChecksumFileChecker digest_check((*it_res)->location.checksum());    
690           (*it_res)->checkers.push_back(digest_check);
691       }
692       
693       // validate job, this throws if not valid
694       validate((*it_res)->location, dest_dir, (*it_res)->checkers);
695
696       if ( ! progress.incr() )
697         ZYPP_THROW(AbortRequestException());
698     } // for each job
699   }
700
701   /** \relates Fetcher::Impl Stream output */
702   inline std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj )
703   {
704       for ( list<FetcherJob_Ptr>::const_iterator it_res = obj._resources.begin(); it_res != obj._resources.end(); ++it_res )
705       {
706           str << *it_res;
707       }
708       return str;
709   }
710
711   Fetcher::Fetcher()
712   : _pimpl( new Impl() )
713   {}
714
715   Fetcher::~Fetcher()
716   {}
717
718   void Fetcher::setOptions( Fetcher::Options options )
719   {
720     _pimpl->setOptions(options);
721   }
722     
723   Fetcher::Options Fetcher::options() const
724   {
725     return _pimpl->options();
726   }
727
728   void Fetcher::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
729   {
730     _pimpl->enqueueDigested(resource, checker);
731   }
732
733   void Fetcher::enqueueDir( const OnMediaLocation &resource,
734                             bool recursive,
735                             const FileChecker &checker )
736   {
737       _pimpl->enqueueDir(resource, recursive, checker);
738   }
739
740   void Fetcher::enqueueDigestedDir( const OnMediaLocation &resource,
741                                     bool recursive,
742                                     const FileChecker &checker )
743   {
744       _pimpl->enqueueDigestedDir(resource, recursive, checker);
745   }
746
747
748   void Fetcher::addIndex( const OnMediaLocation &resource )
749   {
750     _pimpl->addIndex(resource);
751   }
752
753
754   void Fetcher::enqueue( const OnMediaLocation &resource, const FileChecker &checker  )
755   {
756     _pimpl->enqueue(resource, checker);
757   }
758
759   void Fetcher::addCachePath( const Pathname &cache_dir )
760   {
761     _pimpl->addCachePath(cache_dir);
762   }
763
764   void Fetcher::reset()
765   {
766     _pimpl->reset();
767   }
768
769   void Fetcher::start( const Pathname &dest_dir,
770                        MediaSetAccess &media,
771                        const ProgressData::ReceiverFnc & progress_receiver )
772   {
773     _pimpl->start(dest_dir, media, progress_receiver);
774   }
775
776   std::ostream & operator<<( std::ostream & str, const Fetcher & obj )
777   {
778     return str << *obj._pimpl;
779   }
780
781   /////////////////////////////////////////////////////////////////
782 } // namespace zypp
783 ///////////////////////////////////////////////////////////////////
784