- refactoring of Fetcher into smaller methods
[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 <list>
14
15 #include "zypp/base/Easy.h"
16 #include "zypp/base/Logger.h"
17 #include "zypp/base/PtrTypes.h"
18 #include "zypp/base/DefaultIntegral.h"
19 #include "zypp/Fetcher.h"
20 #include "zypp/base/UserRequestException.h"
21
22 using namespace std;
23
24 ///////////////////////////////////////////////////////////////////
25 namespace zypp
26 { /////////////////////////////////////////////////////////////////
27
28   /**
29    * Class to encapsulate the \ref OnMediaLocation object
30    * and the \ref FileChecker together
31    */
32   struct FetcherJob
33   {
34       
35     FetcherJob( const OnMediaLocation &loc )
36       : location(loc)
37       , directory(false)
38       , recursive(false)
39     {
40       //MIL << location << endl;
41     }
42
43     ~FetcherJob()
44     {
45       //MIL << location << " | * " << checkers.size() << endl;
46     }
47
48     OnMediaLocation location;
49     //CompositeFileChecker checkers;
50     list<FileChecker> checkers;
51     bool directory;
52     bool recursive;      
53   };
54
55   typedef shared_ptr<FetcherJob> FetcherJob_Ptr;
56
57   std::ostream & operator<<( std::ostream & str, const FetcherJob_Ptr & obj )
58   {
59     return str << obj->location;
60   }
61
62
63   ///////////////////////////////////////////////////////////////////
64   //
65   //    CLASS NAME : Fetcher::Impl
66   //
67   /** Fetcher implementation. */
68   class Fetcher::Impl
69   {
70     friend std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj );
71   public:
72     Impl() {}
73     ~Impl() {
74       MIL << endl;
75      }
76     
77     void enqueue( const OnMediaLocation &resource, const FileChecker &checker  );
78     void enqueueDir( const OnMediaLocation &resource, bool recursive, const FileChecker &checker );
79     void enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker );
80     void addCachePath( const Pathname &cache_dir );
81     void reset();
82     void start( const Pathname &dest_dir,
83                 MediaSetAccess &media,
84                 const ProgressData::ReceiverFnc & progress_receiver );
85
86     /** Offer default Impl. */
87     static shared_ptr<Impl> nullimpl()
88     {
89       static shared_ptr<Impl> _nullimpl( new Impl );
90       return _nullimpl;
91     }
92   private:
93       /**
94        * tries to provide the file represented by job into dest_dir by
95        * looking at the cache. If success, returns true, and the desired
96        * file should be available on dest_dir
97        */
98       bool provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir );
99       /**
100        * Validates the job against is checkers, by using the file instance
101        * on dest_dir
102        * \throws Exception
103        */
104       void validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers );
105       
106       /**
107        * scan the directory and adds the individual jobs
108        */
109           void addDirJobs( MediaSetAccess &media, const OnMediaLocation &resource,
110                        const Pathname &dest_dir, bool recursive );
111       /**
112        * Provide the resource to \ref dest_dir
113        */
114       void provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir );
115
116   private:
117     friend Impl * rwcowClone<Impl>( const Impl * rhs );
118     /** clone for RWCOW_pointer */
119     Impl * clone() const
120     { return new Impl( *this ); }
121
122     std::list<FetcherJob_Ptr> _resources;
123     std::list<Pathname> _caches;
124   };
125   ///////////////////////////////////////////////////////////////////
126
127   void Fetcher::Impl::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
128   {
129     FetcherJob_Ptr job;
130     job.reset(new FetcherJob(resource));
131     ChecksumFileChecker digest_check(resource.checksum());
132     job->checkers.push_back(digest_check);
133     if ( checker )
134       job->checkers.push_back(checker);
135     _resources.push_back(job);
136   }
137     
138   void Fetcher::Impl::enqueueDir( const OnMediaLocation &resource, 
139                                   bool recursive,
140                                   const FileChecker &checker )
141   {
142     FetcherJob_Ptr job;
143     job.reset(new FetcherJob(resource));
144     if ( checker )
145         job->checkers.push_back(checker);
146     job->directory = true;
147     job->recursive = recursive;
148     _resources.push_back(job);
149   }  
150
151   void Fetcher::Impl::enqueue( const OnMediaLocation &resource, const FileChecker &checker )
152   {
153     FetcherJob_Ptr job;
154     job.reset(new FetcherJob(resource));
155     if ( checker )
156       job->checkers.push_back(checker);
157     _resources.push_back(job);
158   }
159
160   void Fetcher::Impl::reset()
161   {
162     _resources.clear();
163   }
164
165   void Fetcher::Impl::addCachePath( const Pathname &cache_dir )
166   {
167     PathInfo info(cache_dir);
168     if ( info.isExist() )
169     {
170       if ( info.isDir() )
171       {
172         DBG << "Adding fetcher cache: '" << cache_dir << "'." << endl;
173         _caches.push_back(cache_dir);
174       }
175       else
176       {
177         // don't add bad cache directory, just log the error
178         ERR << "Not adding cache: '" << cache_dir << "'. Not a directory." << endl;
179       }
180     }
181     else
182     {
183         ERR << "Not adding cache '" << cache_dir << "'. Path does not exists." << endl;
184     }
185     
186   }
187
188   bool Fetcher::Impl::provideFromCache( const OnMediaLocation &resource, const Pathname &dest_dir )
189   {
190     Pathname dest_full_path = dest_dir + resource.filename();
191           
192     // first check in the destination directory
193     if ( PathInfo(dest_full_path).isExist() )
194     {
195       if ( is_checksum( dest_full_path, resource.checksum() )
196            && (! resource.checksum().empty() ) )
197           return true;
198     }
199     
200     MIL << "start fetcher with " << _caches.size() << " cache directories." << endl;
201     for_ ( it_cache, _caches.begin(), _caches.end() )
202     {
203       // does the current file exists in the current cache?
204       Pathname cached_file = *it_cache + resource.filename();
205       if ( PathInfo( cached_file ).isExist() )
206       {
207         DBG << "File '" << cached_file << "' exist, testing checksum " << resource.checksum() << endl;
208          // check the checksum
209         if ( is_checksum( cached_file, resource.checksum() ) && (! resource.checksum().empty() ) )
210         {
211           // cached
212           MIL << "file " << resource.filename() << " found in previous cache. Using cached copy." << endl;
213           // checksum is already checked.
214           // we could later implement double failover and try to download if file copy fails.
215            // replicate the complete path in the target directory
216           if( dest_full_path != cached_file )
217           {
218             if ( assert_dir( dest_full_path.dirname() ) != 0 )
219               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
220
221             if ( filesystem::hardlink(cached_file, dest_full_path ) != 0 )
222             {
223               WAR << "Can't hardlink '" << cached_file << "' to '" << dest_dir << "'. Trying copying." << endl;
224               if ( filesystem::copy(cached_file, dest_full_path ) != 0 )
225               {
226                 ERR << "Can't copy " << cached_file + " to " + dest_dir << endl;
227                 // try next cache
228                 continue;
229               }
230             }
231           }
232           // found in cache
233           return true;
234         }
235       }
236     } // iterate over caches
237     return false;
238   }
239     
240     void Fetcher::Impl::validate( const OnMediaLocation &resource, const Pathname &dest_dir, const list<FileChecker> &checkers )
241   {
242     // no matter where did we got the file, try to validate it:
243     Pathname localfile = dest_dir + resource.filename();
244     // call the checker function
245     try {
246       MIL << "Checking job [" << localfile << "] (" << checkers.size() << " checkers )" << endl;
247       for ( list<FileChecker>::const_iterator it = checkers.begin();
248             it != checkers.end();
249             ++it )
250       {
251         if (*it)
252         {
253           (*it)(localfile);
254         }
255         else
256         {
257           ERR << "Invalid checker for '" << localfile << "'" << endl;
258         }
259       }
260        
261     }
262     catch ( const FileCheckException &e )
263     {
264       ZYPP_RETHROW(e);
265     }
266     catch ( const Exception &e )
267     {
268       ZYPP_RETHROW(e);
269     }
270     catch (...)
271     {
272       ZYPP_THROW(Exception("Unknown error while validating " + resource.filename().asString()));
273     }
274   }
275
276   void Fetcher::Impl::addDirJobs( MediaSetAccess &media,
277                                   const OnMediaLocation &resource, 
278                                   const Pathname &dest_dir, bool recursive  )
279   {
280       filesystem::DirContent content;
281       
282       media.dirInfo( content, resource.filename(), false /* dots */, resource.medianr());
283
284       for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it )
285       {
286           MIL << (*it).name << endl;
287       }
288       
289       filesystem::DirEntry shafile, shasig;
290       shafile.name = "SHA1SUMS";
291       shafile.name = "SHA1SUMS.asc";
292       shasig.type = filesystem::FT_FILE;
293       shasig.type = filesystem::FT_FILE;
294
295       // look for the SHA1SUMS file
296       if ( find(content.begin(), content.end(), shafile) != content.end() )
297       {
298           MIL << "found checksums file: " << shafile.name << endl;
299           
300           provideToDest(media, resource.filename() + shafile.name, dest_dir);
301           // look for the SHA1SUMS.asc signature
302           if ( find(content.begin(), content.end(), shasig) != content.end() )
303           {
304               provideToDest(media, resource.filename() + shasig.name, dest_dir);
305           }
306           
307       }
308       
309       for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it )
310       {
311           Pathname filename = resource.filename() + it->name;
312
313           switch ( it->type )
314           {
315           case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
316           case filesystem::FT_FILE:
317               enqueue(OnMediaLocation().setFilename(filename), FileChecker());
318               
319               MIL << filename << endl;
320               break;
321           case filesystem::FT_DIR: // newer directory.yast contain at least directory info
322               if ( recursive )
323               {
324                   addDirJobs(media, filename, dest_dir, recursive);
325               } 
326               else 
327               {
328               }
329               break;
330           default:
331               // don't provide devices, sockets, etc.
332               break;
333           }
334       }
335   }
336
337   void Fetcher::Impl::provideToDest( MediaSetAccess &media, const OnMediaLocation &resource, const Pathname &dest_dir )
338   {
339     bool got_from_cache = false;
340       
341     // start look in cache
342     got_from_cache = provideFromCache(resource, dest_dir);
343       
344     if ( ! got_from_cache )
345     {
346       MIL << "Not found in cache, downloading" << endl;
347         
348       // try to get the file from the net
349       try
350       {
351         Pathname tmp_file = media.provideFile(resource);
352         Pathname dest_full_path = dest_dir + resource.filename();
353         if ( assert_dir( dest_full_path.dirname() ) != 0 )
354               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
355         if ( filesystem::copy(tmp_file, dest_full_path ) != 0 )
356         {
357           ZYPP_THROW( Exception("Can't copy " + tmp_file.asString() + " to " + dest_dir.asString()));
358         }
359
360         media.releaseFile(resource); //not needed anymore, only eat space
361       }
362       catch (Exception & excpt_r)
363       {
364         ZYPP_CAUGHT(excpt_r);
365         excpt_r.remember("Can't provide " + resource.filename().asString() + " : " + excpt_r.msg());
366         ZYPP_RETHROW(excpt_r);
367       }
368     }
369     else
370     {
371       // We got the file from cache
372       // continue with next file
373         return;
374     }
375   }  
376
377   void Fetcher::Impl::start( const Pathname &dest_dir,
378                              MediaSetAccess &media,
379                              const ProgressData::ReceiverFnc & progress_receiver )
380   {
381     ProgressData progress(_resources.size());
382     progress.sendTo(progress_receiver);
383
384     for ( list<FetcherJob_Ptr>::const_iterator it_res = _resources.begin(); it_res != _resources.end(); ++it_res )
385     { 
386
387       if ( (*it_res)->directory )
388       {
389           const OnMediaLocation location((*it_res)->location);
390           addDirJobs(media, location, dest_dir, true);
391           continue;
392       }
393
394       provideToDest(media, (*it_res)->location, dest_dir);
395
396       // validate job, this throws if not valid
397       validate((*it_res)->location, dest_dir, (*it_res)->checkers);
398       
399       if ( ! progress.incr() )
400         ZYPP_THROW(AbortRequestException());
401     } // for each job
402   }
403
404   /** \relates Fetcher::Impl Stream output */
405   inline std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj )
406   {
407       for ( list<FetcherJob_Ptr>::const_iterator it_res = obj._resources.begin(); it_res != obj._resources.end(); ++it_res )
408       { 
409           str << *it_res;
410       }
411       return str;
412   }
413
414   ///////////////////////////////////////////////////////////////////
415   //
416   //    CLASS NAME : Fetcher
417   //
418   ///////////////////////////////////////////////////////////////////
419
420   ///////////////////////////////////////////////////////////////////
421   //
422   //    METHOD NAME : Fetcher::Fetcher
423   //    METHOD TYPE : Ctor
424   //
425   Fetcher::Fetcher()
426   : _pimpl( new Impl() )
427   {}
428
429   ///////////////////////////////////////////////////////////////////
430   //
431   //    METHOD NAME : Fetcher::~Fetcher
432   //    METHOD TYPE : Dtor
433   //
434   Fetcher::~Fetcher()
435   {}
436
437   void Fetcher::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
438   {
439     _pimpl->enqueueDigested(resource, checker);
440   }
441
442   void Fetcher::enqueueDir( const OnMediaLocation &resource,
443                             bool recursive,
444                             const FileChecker &checker )
445   {
446       _pimpl->enqueueDir(resource, recursive, checker);
447   }
448
449   void Fetcher::enqueue( const OnMediaLocation &resource, const FileChecker &checker  )
450   {
451     _pimpl->enqueue(resource, checker);
452   }
453
454   void Fetcher::addCachePath( const Pathname &cache_dir )
455   {
456     _pimpl->addCachePath(cache_dir);
457   }
458
459   void Fetcher::reset()
460   {
461     _pimpl->reset();
462   }
463
464   void Fetcher::start( const Pathname &dest_dir,
465                        MediaSetAccess &media,
466                        const ProgressData::ReceiverFnc & progress_receiver )
467   {
468     _pimpl->start(dest_dir, media, progress_receiver);
469   }
470
471
472   /******************************************************************
473   **
474   **    FUNCTION NAME : operator<<
475   **    FUNCTION TYPE : std::ostream &
476   */
477   std::ostream & operator<<( std::ostream & str, const Fetcher & obj )
478   {
479     return str << *obj._pimpl;
480   }
481
482   /////////////////////////////////////////////////////////////////
483 } // namespace zypp
484 ///////////////////////////////////////////////////////////////////
485