- refactor fetcher big loop method in 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     FetcherJob( const OnMediaLocation &loc )
35       : location(loc)
36       , directory(false)
37       , recursive(false)
38     {
39       //MIL << location << endl;
40     }
41
42     ~FetcherJob()
43     {
44       //MIL << location << " | * " << checkers.size() << endl;
45     }
46
47     OnMediaLocation location;
48     //CompositeFileChecker checkers;
49     list<FileChecker> checkers;
50     bool directory;
51     bool recursive;      
52   };
53
54   typedef shared_ptr<FetcherJob> FetcherJob_Ptr;
55
56   ///////////////////////////////////////////////////////////////////
57   //
58   //    CLASS NAME : Fetcher::Impl
59   //
60   /** Fetcher implementation. */
61   class Fetcher::Impl
62   {
63
64   public:
65     Impl() {}
66     ~Impl() {
67       MIL << endl;
68      }
69     
70     void enqueue( const OnMediaLocation &resource, const FileChecker &checker  );
71     void enqueueDir( const OnMediaLocation &resource, bool recursive, const FileChecker &checker );
72     void enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker );
73     void addCachePath( const Pathname &cache_dir );
74     void reset();
75     void start( const Pathname &dest_dir,
76                 MediaSetAccess &media,
77                 const ProgressData::ReceiverFnc & progress_receiver );
78
79     /** Offer default Impl. */
80     static shared_ptr<Impl> nullimpl()
81     {
82       static shared_ptr<Impl> _nullimpl( new Impl );
83       return _nullimpl;
84     }
85   private:
86       /**
87        * tries to provide the file represented by job into dest_dir by
88        * looking at the cache. If success, returns true, and the desired
89        * file should be available on dest_dir
90        */
91       bool provideFromCache( FetcherJob_Ptr job, const Pathname &dest_dir );
92       /**
93        * Validates the job against is checkers, by using the file instance
94        * on dest_dir
95        * \throws Exception
96        */
97       void validate( FetcherJob_Ptr job, const Pathname &dest_dir );
98   private:
99     friend Impl * rwcowClone<Impl>( const Impl * rhs );
100     /** clone for RWCOW_pointer */
101     Impl * clone() const
102     { return new Impl( *this ); }
103
104     std::list<FetcherJob_Ptr> _resources;
105     std::list<Pathname> _caches;
106   };
107   ///////////////////////////////////////////////////////////////////
108
109   void Fetcher::Impl::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
110   {
111     FetcherJob_Ptr job;
112     job.reset(new FetcherJob(resource));
113     ChecksumFileChecker digest_check(resource.checksum());
114     job->checkers.push_back(digest_check);
115     if ( checker )
116       job->checkers.push_back(checker);
117     _resources.push_back(job);
118   }
119     
120   void Fetcher::Impl::enqueueDir( const OnMediaLocation &resource, 
121                                   bool recursive,
122                                   const FileChecker &checker )
123   {
124     FetcherJob_Ptr job;
125     job.reset(new FetcherJob(resource));
126     if ( checker )
127         job->checkers.push_back(checker);
128     job->directory = true;
129     job->recursive = recursive;
130     _resources.push_back(job);
131   }  
132
133   void Fetcher::Impl::enqueue( const OnMediaLocation &resource, const FileChecker &checker )
134   {
135     FetcherJob_Ptr job;
136     job.reset(new FetcherJob(resource));
137     if ( checker )
138       job->checkers.push_back(checker);
139     _resources.push_back(job);
140   }
141
142   void Fetcher::Impl::reset()
143   {
144     _resources.clear();
145   }
146
147   void Fetcher::Impl::addCachePath( const Pathname &cache_dir )
148   {
149     PathInfo info(cache_dir);
150     if ( info.isExist() )
151     {
152       if ( info.isDir() )
153       {
154         DBG << "Adding fetcher cache: '" << cache_dir << "'." << endl;
155         _caches.push_back(cache_dir);
156       }
157       else
158       {
159         // don't add bad cache directory, just log the error
160         ERR << "Not adding cache: '" << cache_dir << "'. Not a directory." << endl;
161       }
162     }
163     else
164     {
165         ERR << "Not adding cache '" << cache_dir << "'. Path does not exists." << endl;
166     }
167     
168   }
169
170   bool Fetcher::Impl::provideFromCache( FetcherJob_Ptr job, const Pathname &dest_dir )
171   {
172     MIL << "start fetcher with " << _caches.size() << " cache directories." << endl;
173     for_ ( it_cache, _caches.begin(), _caches.end() )
174     {
175       // does the current file exists in the current cache?
176       Pathname cached_file = *it_cache + job->location.filename();
177       if ( PathInfo( cached_file ).isExist() )
178       {
179         DBG << "File '" << cached_file << "' exist, testing checksum " << job->location.checksum() << endl;
180          // check the checksum
181         if ( is_checksum( cached_file, job->location.checksum() ) && (! job->location.checksum().empty() ) )
182         {
183           // cached
184           MIL << "file " << job->location.filename() << " found in previous cache. Using cached copy." << endl;
185           // checksum is already checked.
186           // we could later implement double failover and try to download if file copy fails.
187            // replicate the complete path in the target directory
188           Pathname dest_full_path = dest_dir + job->location.filename();
189           if( dest_full_path != cached_file )
190           {
191             if ( assert_dir( dest_full_path.dirname() ) != 0 )
192               ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
193
194             if ( filesystem::hardlink(cached_file, dest_full_path ) != 0 )
195             {
196               WAR << "Can't hardlink '" << cached_file << "' to '" << dest_dir << "'. Trying copying." << endl;
197               if ( filesystem::copy(cached_file, dest_full_path ) != 0 )
198               {
199                 ERR << "Can't copy " << cached_file + " to " + dest_dir << endl;
200                 // try next cache
201                 continue;
202               }
203             }
204           }
205           // found in cache
206           return true;
207         }
208       }
209     } // iterate over caches
210     return false;
211   }
212     
213   void Fetcher::Impl::validate( FetcherJob_Ptr job, const Pathname &dest_dir )
214   {
215     // no matter where did we got the file, try to validate it:
216     Pathname localfile = dest_dir + job->location.filename();
217     // call the checker function
218     try {
219       MIL << "Checking job [" << localfile << "] (" << job->checkers.size() << " checkers )" << endl;
220       for ( list<FileChecker>::const_iterator it = job->checkers.begin();
221             it != job->checkers.end();
222             ++it )
223       {
224         if (*it)
225         {
226           (*it)(localfile);
227         }
228         else
229         {
230           ERR << "Invalid checker for '" << localfile << "'" << endl;
231         }
232       }
233        
234     }
235     catch ( const FileCheckException &e )
236     {
237       ZYPP_RETHROW(e);
238     }
239     catch ( const Exception &e )
240     {
241       ZYPP_RETHROW(e);
242     }
243     catch (...)
244     {
245       ZYPP_THROW(Exception("Unknown error while validating " + job->location.filename().asString()));
246     }
247   }
248
249   void Fetcher::Impl::start( const Pathname &dest_dir,
250                              MediaSetAccess &media,
251                              const ProgressData::ReceiverFnc & progress_receiver )
252   {
253     ProgressData progress(_resources.size());
254     progress.sendTo(progress_receiver);
255
256     for ( list<FetcherJob_Ptr>::const_iterator it_res = _resources.begin(); it_res != _resources.end(); ++it_res )
257     { 
258       bool got_from_cache = false;
259       
260       // start look in cache
261       got_from_cache = provideFromCache((*it_res), dest_dir);
262       
263       if ( ! got_from_cache )
264       {
265         MIL << "Not found in cache, downloading" << endl;
266         
267         // try to get the file from the net
268         try
269         {
270           Pathname tmp_file = media.provideFile((*it_res)->location);
271           Pathname dest_full_path = dest_dir + (*it_res)->location.filename();
272           if ( assert_dir( dest_full_path.dirname() ) != 0 )
273                 ZYPP_THROW( Exception("Can't create " + dest_full_path.dirname().asString()));
274           if ( filesystem::copy(tmp_file, dest_full_path ) != 0 )
275           {
276             ZYPP_THROW( Exception("Can't copy " + tmp_file.asString() + " to " + dest_dir.asString()));
277           }
278
279           media.releaseFile((*it_res)->location); //not needed anymore, only eat space
280         }
281         catch (Exception & excpt_r)
282         {
283           ZYPP_CAUGHT(excpt_r);
284           excpt_r.remember("Can't provide " + (*it_res)->location.filename().asString() + " : " + excpt_r.msg());
285           ZYPP_RETHROW(excpt_r);
286         }
287       }
288       else
289       {
290         // We got the file from cache
291         // continue with next file
292         continue;
293       }
294
295       // validate job, this throws if not valid
296       validate(*it_res, dest_dir);
297       
298       if ( ! progress.incr() )
299         ZYPP_THROW(AbortRequestException());
300     } // for each job
301   }
302
303   /** \relates Fetcher::Impl Stream output */
304   inline std::ostream & operator<<( std::ostream & str, const Fetcher::Impl & obj )
305   {
306     return str << "Fetcher::Impl";
307   }
308
309   ///////////////////////////////////////////////////////////////////
310   //
311   //    CLASS NAME : Fetcher
312   //
313   ///////////////////////////////////////////////////////////////////
314
315   ///////////////////////////////////////////////////////////////////
316   //
317   //    METHOD NAME : Fetcher::Fetcher
318   //    METHOD TYPE : Ctor
319   //
320   Fetcher::Fetcher()
321   : _pimpl( new Impl() )
322   {}
323
324   ///////////////////////////////////////////////////////////////////
325   //
326   //    METHOD NAME : Fetcher::~Fetcher
327   //    METHOD TYPE : Dtor
328   //
329   Fetcher::~Fetcher()
330   {}
331
332   void Fetcher::enqueueDigested( const OnMediaLocation &resource, const FileChecker &checker )
333   {
334     _pimpl->enqueueDigested(resource, checker);
335   }
336
337   void Fetcher::enqueueDir( const OnMediaLocation &resource,
338                             bool recursive,
339                             const FileChecker &checker )
340   {
341       _pimpl->enqueueDir(resource, recursive, checker);
342   }
343
344   void Fetcher::enqueue( const OnMediaLocation &resource, const FileChecker &checker  )
345   {
346     _pimpl->enqueue(resource, checker);
347   }
348
349   void Fetcher::addCachePath( const Pathname &cache_dir )
350   {
351     _pimpl->addCachePath(cache_dir);
352   }
353
354   void Fetcher::reset()
355   {
356     _pimpl->reset();
357   }
358
359   void Fetcher::start( const Pathname &dest_dir,
360                        MediaSetAccess &media,
361                        const ProgressData::ReceiverFnc & progress_receiver )
362   {
363     _pimpl->start(dest_dir, media, progress_receiver);
364   }
365
366
367   /******************************************************************
368   **
369   **    FUNCTION NAME : operator<<
370   **    FUNCTION TYPE : std::ostream &
371   */
372   std::ostream & operator<<( std::ostream & str, const Fetcher & obj )
373   {
374     return str << *obj._pimpl;
375   }
376
377   /////////////////////////////////////////////////////////////////
378 } // namespace zypp
379 ///////////////////////////////////////////////////////////////////
380