libzypp  11.13.5
RepoManager.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
13 #include <cstdlib>
14 #include <iostream>
15 #include <fstream>
16 #include <sstream>
17 #include <list>
18 #include <map>
19 #include <algorithm>
20 
21 #include "zypp/base/InputStream.h"
22 #include "zypp/base/LogTools.h"
23 #include "zypp/base/Gettext.h"
24 #include "zypp/base/Function.h"
25 #include "zypp/base/Regex.h"
26 #include "zypp/PathInfo.h"
27 #include "zypp/TmpPath.h"
28 
29 #include "zypp/ServiceInfo.h"
31 #include "zypp/RepoManager.h"
32 
35 #include "zypp/MediaSetAccess.h"
36 #include "zypp/ExternalProgram.h"
37 #include "zypp/ManagedFile.h"
38 
41 #include "zypp/repo/ServiceRepos.h"
46 
47 #include "zypp/Target.h" // for Target::targetDistribution() for repo index services
48 #include "zypp/ZYppFactory.h" // to get the Target from ZYpp instance
49 #include "zypp/HistoryLog.h" // to write history :O)
50 
51 #include "zypp/ZYppCallbacks.h"
52 
53 #include "sat/Pool.h"
54 
55 using std::endl;
56 using std::string;
57 using namespace zypp::repo;
58 
60 namespace zypp
61 {
62 
63  namespace
64  {
68  class MediaMounter
69  {
70  public:
72  MediaMounter( const Url & url_r )
73  {
74  media::MediaManager mediamanager;
75  _mid = mediamanager.open( url_r );
76  mediamanager.attach( _mid );
77  }
78 
80  ~MediaMounter()
81  {
82  media::MediaManager mediamanager;
83  mediamanager.release( _mid );
84  mediamanager.close( _mid );
85  }
86 
91  Pathname getPathName( const Pathname & path_r = Pathname() ) const
92  {
93  media::MediaManager mediamanager;
94  return mediamanager.localPath( _mid, path_r );
95  }
96 
97  private:
99  };
100 
102  template <class Iterator>
103  inline bool foundAliasIn( const std::string & alias_r, Iterator begin_r, Iterator end_r )
104  {
105  for_( it, begin_r, end_r )
106  if ( it->alias() == alias_r )
107  return true;
108  return false;
109  }
111  template <class Container>
112  inline bool foundAliasIn( const std::string & alias_r, const Container & cont_r )
113  { return foundAliasIn( alias_r, cont_r.begin(), cont_r.end() ); }
114 
116  template <class Iterator>
117  inline Iterator findAlias( const std::string & alias_r, Iterator begin_r, Iterator end_r )
118  {
119  for_( it, begin_r, end_r )
120  if ( it->alias() == alias_r )
121  return it;
122  return end_r;
123  }
125  template <class Container>
126  inline typename Container::iterator findAlias( const std::string & alias_r, Container & cont_r )
127  { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
129  template <class Container>
130  inline typename Container::const_iterator findAlias( const std::string & alias_r, const Container & cont_r )
131  { return findAlias( alias_r, cont_r.begin(), cont_r.end() ); }
132  }
133 
135  //
136  // CLASS NAME : RepoManagerOptions
137  //
139 
140  RepoManagerOptions::RepoManagerOptions( const Pathname & root_r )
141  {
142  repoCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoCachePath() );
143  repoRawCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoMetadataPath() );
144  repoSolvCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoSolvfilesPath() );
145  repoPackagesCachePath = Pathname::assertprefix( root_r, ZConfig::instance().repoPackagesPath() );
146  knownReposPath = Pathname::assertprefix( root_r, ZConfig::instance().knownReposPath() );
147  knownServicesPath = Pathname::assertprefix( root_r, ZConfig::instance().knownServicesPath() );
148  pluginsPath = Pathname::assertprefix( root_r, ZConfig::instance().pluginsPath() );
149  probe = ZConfig::instance().repo_add_probe();
150 
151  rootDir = root_r;
152  }
153 
155  {
156  RepoManagerOptions ret;
157  ret.repoCachePath = root_r;
158  ret.repoRawCachePath = root_r/"raw";
159  ret.repoSolvCachePath = root_r/"solv";
160  ret.repoPackagesCachePath = root_r/"packages";
161  ret.knownReposPath = root_r/"repos.d";
162  ret.knownServicesPath = root_r/"services.d";
163  ret.pluginsPath = root_r/"plugins";
164  ret.rootDir = root_r;
165  return ret;
166  }
167 
169 
186  {
188  {}
189 
190  RepoCollector(const std::string & targetDistro_)
191  : targetDistro(targetDistro_)
192  {}
193 
194  bool collect( const RepoInfo &repo )
195  {
196  // skip repositories meant for other distros than specified
197  if (!targetDistro.empty()
198  && !repo.targetDistribution().empty()
199  && repo.targetDistribution() != targetDistro)
200  {
201  MIL
202  << "Skipping repository meant for '" << targetDistro
203  << "' distribution (current distro is '"
204  << repo.targetDistribution() << "')." << endl;
205 
206  return true;
207  }
208 
209  repos.push_back(repo);
210  return true;
211  }
212 
213  RepoInfoList repos;
214  std::string targetDistro;
215  };
216 
218 
224  static std::list<RepoInfo> repositories_in_file( const Pathname & file )
225  {
226  MIL << "repo file: " << file << endl;
227  RepoCollector collector;
228  parser::RepoFileReader parser( file, bind( &RepoCollector::collect, &collector, _1 ) );
229  return collector.repos;
230  }
231 
233 
242  static std::list<RepoInfo> repositories_in_dir( const Pathname &dir )
243  {
244  MIL << "directory " << dir << endl;
245  std::list<RepoInfo> repos;
246  std::list<Pathname> entries;
247  if ( filesystem::readdir( entries, dir, false ) != 0 )
248  {
249  // TranslatorExplanation '%s' is a pathname
250  ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
251  }
252 
253  str::regex allowedRepoExt("^\\.repo(_[0-9]+)?$");
254  for ( std::list<Pathname>::const_iterator it = entries.begin(); it != entries.end(); ++it )
255  {
256  if (str::regex_match(it->extension(), allowedRepoExt))
257  {
258  std::list<RepoInfo> tmp = repositories_in_file( *it );
259  repos.insert( repos.end(), tmp.begin(), tmp.end() );
260 
261  //std::copy( collector.repos.begin(), collector.repos.end(), std::back_inserter(repos));
262  //MIL << "ok" << endl;
263  }
264  }
265  return repos;
266  }
267 
269 
270  std::list<RepoInfo> readRepoFile(const Url & repo_file)
271  {
272  // no interface to download a specific file, using workaround:
274  Url url(repo_file);
275  Pathname path(url.getPathName());
276  url.setPathName ("/");
277  MediaSetAccess access(url);
278  Pathname local = access.provideFile(path);
279 
280  DBG << "reading repo file " << repo_file << ", local path: " << local << endl;
281 
282  return repositories_in_file(local);
283  }
284 
286 
287  inline void assert_alias( const RepoInfo & info )
288  {
289  if ( info.alias().empty() )
291  // bnc #473834. Maybe we can match the alias against a regex to define
292  // and check for valid aliases
293  if ( info.alias()[0] == '.')
295  info, _("Repository alias cannot start with dot.")));
296  }
297 
298  inline void assert_alias( const ServiceInfo & info )
299  {
300  if ( info.alias().empty() )
302  // bnc #473834. Maybe we can match the alias against a regex to define
303  // and check for valid aliases
304  if ( info.alias()[0] == '.')
306  info, _("Service alias cannot start with dot.")));
307  }
308 
310 
311  inline void assert_urls( const RepoInfo & info )
312  {
313  if ( info.baseUrlsEmpty() )
314  ZYPP_THROW( RepoNoUrlException( info ) );
315  }
316 
317  inline void assert_url( const ServiceInfo & info )
318  {
319  if ( ! info.url().isValid() )
321  }
322 
324 
329  inline Pathname rawcache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
330  {
331  assert_alias(info);
332  return opt.repoRawCachePath / info.escaped_alias();
333  }
334 
343  inline Pathname rawproductdata_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
344  {
345  assert_alias(info);
346  return opt.repoRawCachePath / info.escaped_alias() / info.path();
347  }
348 
349 
353  inline Pathname packagescache_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info )
354  {
355  assert_alias(info);
356  return opt.repoPackagesCachePath / info.escaped_alias();
357  }
358 
362  inline Pathname solv_path_for_repoinfo( const RepoManagerOptions &opt, const RepoInfo &info)
363  {
364  assert_alias(info);
365  return opt.repoSolvCachePath / info.escaped_alias();
366  }
367 
369 
372  {
373  public:
374  typedef std::set<ServiceInfo> ServiceSet;
375 
376  ServiceCollector( ServiceSet & services_r )
377  : _services( services_r )
378  {}
379 
380  bool operator()( const ServiceInfo & service_r ) const
381  {
382  _services.insert( service_r );
383  return true;
384  }
385 
386  private:
388  };
389 
391 
393  //
394  // CLASS NAME : RepoManager::Impl
395  //
397 
402  {
403  Impl( const RepoManagerOptions &opt )
404  : options(opt)
405  {
406  init_knownServices();
407  init_knownRepositories();
408  }
409 
410 
412 
414 
416 
417  public:
418 
419  void saveService( ServiceInfo & service ) const;
420 
421  Pathname generateNonExistingName( const Pathname &dir,
422  const std::string &basefilename ) const;
423 
424  std::string generateFilename( const RepoInfo & info ) const;
425  std::string generateFilename( const ServiceInfo & info ) const;
426 
427 
428  private:
429  void init_knownServices();
430  void init_knownRepositories();
431 
432  private:
433  friend Impl * rwcowClone<Impl>( const Impl * rhs );
435  Impl * clone() const
436  { return new Impl( *this ); }
437  };
438 
440 
442  inline std::ostream & operator<<( std::ostream & str, const RepoManager::Impl & obj )
443  {
444  return str << "RepoManager::Impl";
445  }
446 
448 
450  {
451  filesystem::assert_dir( options.knownServicesPath );
452  Pathname servfile = generateNonExistingName( options.knownServicesPath,
453  generateFilename( service ) );
454  service.setFilepath( servfile );
455 
456  MIL << "saving service in " << servfile << endl;
457 
458  std::ofstream file( servfile.c_str() );
459  if ( !file )
460  {
461  // TranslatorExplanation '%s' is a filename
462  ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), servfile.c_str() )));
463  }
464  service.dumpAsIniOn( file );
465  MIL << "done" << endl;
466  }
467 
483  Pathname RepoManager::Impl::generateNonExistingName( const Pathname & dir,
484  const std::string & basefilename ) const
485  {
486  std::string final_filename = basefilename;
487  int counter = 1;
488  while ( PathInfo(dir + final_filename).isExist() )
489  {
490  final_filename = basefilename + "_" + str::numstring(counter);
491  counter++;
492  }
493  return dir + Pathname(final_filename);
494  }
495 
497 
505  std::string RepoManager::Impl::generateFilename( const RepoInfo & info ) const
506  {
507  std::string filename = info.alias();
508  // replace slashes with underscores
509  str::replaceAll( filename, "/", "_" );
510 
511  filename = Pathname(filename).extend(".repo").asString();
512  MIL << "generating filename for repo [" << info.alias() << "] : '" << filename << "'" << endl;
513  return filename;
514  }
515 
516  std::string RepoManager::Impl::generateFilename( const ServiceInfo & info ) const
517  {
518  std::string filename = info.alias();
519  // replace slashes with underscores
520  str::replaceAll( filename, "/", "_" );
521 
522  filename = Pathname(filename).extend(".service").asString();
523  MIL << "generating filename for service [" << info.alias() << "] : '" << filename << "'" << endl;
524  return filename;
525  }
526 
527 
529  {
530  Pathname dir = options.knownServicesPath;
531  std::list<Pathname> entries;
532  if (PathInfo(dir).isExist())
533  {
534  if ( filesystem::readdir( entries, dir, false ) != 0 )
535  {
536  // TranslatorExplanation '%s' is a pathname
537  ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir.c_str())));
538  }
539 
540  //str::regex allowedServiceExt("^\\.service(_[0-9]+)?$");
541  for_(it, entries.begin(), entries.end() )
542  {
544  }
545  }
546 
547  repo::PluginServices(options.pluginsPath/"services", ServiceCollector(services));
548  }
549 
551  {
552  MIL << "start construct known repos" << endl;
553 
554  if ( PathInfo(options.knownReposPath).isExist() )
555  {
556  RepoInfoList repol = repositories_in_dir(options.knownReposPath);
557  std::list<string> repo_esc_aliases;
558  std::list<string> entries;
559  for ( RepoInfoList::iterator it = repol.begin();
560  it != repol.end();
561  ++it )
562  {
563  // set the metadata path for the repo
564  Pathname metadata_path = rawcache_path_for_repoinfo(options, (*it));
565  (*it).setMetadataPath(metadata_path);
566 
567  // set the downloaded packages path for the repo
568  Pathname packages_path = packagescache_path_for_repoinfo(options, (*it));
569  (*it).setPackagesPath(packages_path);
570 
571  repos.insert(*it);
572  repo_esc_aliases.push_back(it->escaped_alias());
573  }
574 
575  // delete metadata folders without corresponding repo (e.g. old tmp directories)
576  if ( filesystem::readdir( entries, options.repoRawCachePath, false ) == 0 )
577  {
578  std::set<string> oldfiles;
579  repo_esc_aliases.sort();
580  entries.sort();
581  set_difference(entries.begin(), entries.end(), repo_esc_aliases.begin(), repo_esc_aliases.end(), std::inserter(oldfiles, oldfiles.end()));
582  for_(it, oldfiles.begin(), oldfiles.end())
583  {
584  filesystem::recursive_rmdir(options.repoRawCachePath / *it);
585  }
586  }
587  }
588 
589  MIL << "end construct known repos" << endl;
590  }
591 
593  //
594  // CLASS NAME : RepoManager
595  //
597 
599  : _pimpl( new Impl(opt) )
600  {}
601 
603 
605  {}
606 
608 
610  { return _pimpl->repos.empty(); }
611 
613  { return _pimpl->repos.size(); }
614 
616  { return _pimpl->repos.begin(); }
617 
619  { return _pimpl->repos.end(); }
620 
621  RepoInfo RepoManager::getRepo( const std::string & alias ) const
622  {
623  for_( it, repoBegin(), repoEnd() )
624  if ( it->alias() == alias )
625  return *it;
626  return RepoInfo::noRepo;
627  }
628 
629  bool RepoManager::hasRepo( const std::string & alias ) const
630  {
631  for_( it, repoBegin(), repoEnd() )
632  if ( it->alias() == alias )
633  return true;
634  return false;
635  }
636 
637  std::string RepoManager::makeStupidAlias( const Url & url_r )
638  {
639  std::string ret( url_r.getScheme() );
640  if ( ret.empty() )
641  ret = "repo-";
642  else
643  ret += "-";
644 
645  std::string host( url_r.getHost() );
646  if ( ! host.empty() )
647  {
648  ret += host;
649  ret += "-";
650  }
651 
652  static Date::ValueType serial = Date::now();
653  ret += Digest::digest( Digest::sha1(), str::hexstring( ++serial ) +url_r.asCompleteString() ).substr(0,8);
654  return ret;
655  }
656 
658 
659  Pathname RepoManager::metadataPath( const RepoInfo &info ) const
660  {
661  return rawcache_path_for_repoinfo(_pimpl->options, info );
662  }
663 
664  Pathname RepoManager::packagesPath( const RepoInfo &info ) const
665  {
667  }
668 
670 
672  {
673  Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
674  Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
675  RepoType repokind = info.type();
676  RepoStatus status;
677 
678  switch ( repokind.toEnum() )
679  {
680  case RepoType::NONE_e:
681  // unknown, probe the local metadata
682  repokind = probe( productdatapath.asUrl() );
683  break;
684  default:
685  break;
686  }
687 
688  switch ( repokind.toEnum() )
689  {
690  case RepoType::RPMMD_e :
691  {
692  status = RepoStatus( productdatapath + "/repodata/repomd.xml");
693  }
694  break;
695 
696  case RepoType::YAST2_e :
697  {
698  status = RepoStatus( productdatapath + "/content") && (RepoStatus( mediarootpath + "/media.1/media"));
699  }
700  break;
701 
702  case RepoType::RPMPLAINDIR_e :
703  {
704  if ( PathInfo(Pathname(productdatapath + "/cookie")).isExist() )
705  status = RepoStatus( productdatapath + "/cookie");
706  }
707  break;
708 
709  case RepoType::NONE_e :
710  // Return default RepoStatus in case of RepoType::NONE
711  // indicating it should be created?
712  // ZYPP_THROW(RepoUnknownTypeException());
713  break;
714  }
715  return status;
716  }
717 
719  {
720  Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
721 
722  RepoType repokind = info.type();
723  if ( repokind.toEnum() == RepoType::NONE_e )
724  // unknown, probe the local metadata
725  repokind = probe( productdatapath.asUrl() );
726  // if still unknown, just return
727  if (repokind == RepoType::NONE_e)
728  return;
729 
730  Pathname p;
731  switch ( repokind.toEnum() )
732  {
733  case RepoType::RPMMD_e :
734  p = Pathname(productdatapath + "/repodata/repomd.xml");
735  break;
736 
737  case RepoType::YAST2_e :
738  p = Pathname(productdatapath + "/content");
739  break;
740 
741  case RepoType::RPMPLAINDIR_e :
742  p = Pathname(productdatapath + "/cookie");
743  break;
744 
745  case RepoType::NONE_e :
746  default:
747  break;
748  }
749 
750  // touch the file, ignore error (they are logged anyway)
752  }
753 
755  const RepoInfo &info,
756  const Url &url,
757  RawMetadataRefreshPolicy policy )
758  {
759  assert_alias(info);
760 
761  RepoStatus oldstatus;
762  RepoStatus newstatus;
763 
764  try
765  {
766  MIL << "Going to try to check whether refresh is needed for " << url << endl;
767 
768  // first check old (cached) metadata
769  Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
770  filesystem::assert_dir(mediarootpath);
771  oldstatus = metadataStatus(info);
772 
773  if ( oldstatus.empty() )
774  {
775  MIL << "No cached metadata, going to refresh" << endl;
776  return REFRESH_NEEDED;
777  }
778 
779  {
780  std::string scheme( url.getScheme() );
781  if ( scheme == "cd" || scheme == "dvd" )
782  {
783  MIL << "never refresh CD/DVD" << endl;
784  return REPO_UP_TO_DATE;
785  }
786  }
787 
788  // now we've got the old (cached) status, we can decide repo.refresh.delay
789  if (policy != RefreshForced && policy != RefreshIfNeededIgnoreDelay)
790  {
791  // difference in seconds
792  double diff = difftime(
794  (Date::ValueType)oldstatus.timestamp()) / 60;
795 
796  DBG << "oldstatus: " << (Date::ValueType)oldstatus.timestamp() << endl;
797  DBG << "current time: " << (Date::ValueType)Date::now() << endl;
798  DBG << "last refresh = " << diff << " minutes ago" << endl;
799 
800  if ( diff < ZConfig::instance().repo_refresh_delay() )
801  {
802  if ( diff < 0 )
803  {
804  WAR << "Repository '" << info.alias() << "' was refreshed in the future!" << endl;
805  }
806  else
807  {
808  MIL << "Repository '" << info.alias()
809  << "' has been refreshed less than repo.refresh.delay ("
811  << ") minutes ago. Advising to skip refresh" << endl;
812  return REPO_CHECK_DELAYED;
813  }
814  }
815  }
816 
817  // To test the new matadta create temp dir as sibling of mediarootpath
818  filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
819 
820  repo::RepoType repokind = info.type();
821  // if the type is unknown, try probing.
822  switch ( repokind.toEnum() )
823  {
824  case RepoType::NONE_e:
825  // unknown, probe it \todo respect productdir
826  repokind = probe( url, info.path() );
827  break;
828  default:
829  break;
830  }
831 
832  if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
833  ( repokind.toEnum() == RepoType::YAST2_e ) )
834  {
835  MediaSetAccess media(url);
836  shared_ptr<repo::Downloader> downloader_ptr;
837 
838  if ( repokind.toEnum() == RepoType::RPMMD_e )
839  downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
840  else
841  downloader_ptr.reset( new susetags::Downloader(info, mediarootpath));
842 
843  RepoStatus newstatus = downloader_ptr->status(media);
844  bool refresh = false;
845  if ( oldstatus.checksum() == newstatus.checksum() )
846  {
847  MIL << "repo has not changed" << endl;
848  if ( policy == RefreshForced )
849  {
850  MIL << "refresh set to forced" << endl;
851  refresh = true;
852  }
853  }
854  else
855  {
856  MIL << "repo has changed, going to refresh" << endl;
857  refresh = true;
858  }
859 
860  if (!refresh)
861  touchIndexFile(info);
862 
863  return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
864  }
865  else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
866  {
867  MediaMounter media( url );
868  RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
869  bool refresh = false;
870  if ( oldstatus.checksum() == newstatus.checksum() )
871  {
872  MIL << "repo has not changed" << endl;
873  if ( policy == RefreshForced )
874  {
875  MIL << "refresh set to forced" << endl;
876  refresh = true;
877  }
878  }
879  else
880  {
881  MIL << "repo has changed, going to refresh" << endl;
882  refresh = true;
883  }
884 
885  if (!refresh)
886  touchIndexFile(info);
887 
888  return refresh ? REFRESH_NEEDED : REPO_UP_TO_DATE;
889  }
890  else
891  {
893  }
894  }
895  catch ( const Exception &e )
896  {
897  ZYPP_CAUGHT(e);
898  ERR << "refresh check failed for " << url << endl;
899  ZYPP_RETHROW(e);
900  }
901 
902  return REFRESH_NEEDED; // default
903  }
904 
907  const ProgressData::ReceiverFnc & progress )
908  {
909  assert_alias(info);
910  assert_urls(info);
911 
912  // we will throw this later if no URL checks out fine
913  RepoException rexception(_("Valid metadata not found at specified URL(s)"));
914 
915  // try urls one by one
916  for ( RepoInfo::urls_const_iterator it = info.baseUrlsBegin(); it != info.baseUrlsEnd(); ++it )
917  {
918  try
919  {
920  Url url(*it);
921 
922  // check whether to refresh metadata
923  // if the check fails for this url, it throws, so another url will be checked
924  if (checkIfToRefreshMetadata(info, url, policy)!=REFRESH_NEEDED)
925  return;
926 
927  MIL << "Going to refresh metadata from " << url << endl;
928 
929  repo::RepoType repokind = info.type();
930 
931  // if the type is unknown, try probing.
932  switch ( repokind.toEnum() )
933  {
934  case RepoType::NONE_e:
935  // unknown, probe it
936  repokind = probe( *it, info.path() );
937 
938  if (repokind.toEnum() != RepoType::NONE_e)
939  {
940  // Adjust the probed type in RepoInfo
941  info.setProbedType( repokind ); // lazy init!
942  //save probed type only for repos in system
943  for_( it, repoBegin(), repoEnd() )
944  {
945  if ( info.alias() == (*it).alias() )
946  {
947  RepoInfo modifiedrepo = info;
948  modifiedrepo.setType( repokind );
949  modifyRepository( info.alias(), modifiedrepo );
950  break;
951  }
952  }
953  }
954  break;
955  default:
956  break;
957  }
958 
959  Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
960  if( filesystem::assert_dir(mediarootpath) )
961  {
962  Exception ex(str::form( _("Can't create %s"), mediarootpath.c_str()) );
963  ZYPP_THROW(ex);
964  }
965 
966  // create temp dir as sibling of mediarootpath
967  filesystem::TmpDir tmpdir( filesystem::TmpDir::makeSibling( mediarootpath ) );
968  if( tmpdir.path().empty() )
969  {
970  Exception ex(_("Can't create metadata cache directory."));
971  ZYPP_THROW(ex);
972  }
973 
974  if ( ( repokind.toEnum() == RepoType::RPMMD_e ) ||
975  ( repokind.toEnum() == RepoType::YAST2_e ) )
976  {
977  MediaSetAccess media(url);
978  shared_ptr<repo::Downloader> downloader_ptr;
979 
980  MIL << "Creating downloader for [ " << info.alias() << " ]" << endl;
981 
982  if ( repokind.toEnum() == RepoType::RPMMD_e )
983  downloader_ptr.reset(new yum::Downloader(info, mediarootpath));
984  else
985  downloader_ptr.reset( new susetags::Downloader(info, mediarootpath) );
986 
993  for_( it, repoBegin(), repoEnd() )
994  {
995  Pathname cachepath(rawcache_path_for_repoinfo( _pimpl->options, *it ));
996  if ( PathInfo(cachepath).isExist() )
997  downloader_ptr->addCachePath(cachepath);
998  }
999 
1000  downloader_ptr->download( media, tmpdir.path() );
1001  }
1002  else if ( repokind.toEnum() == RepoType::RPMPLAINDIR_e )
1003  {
1004  MediaMounter media( url );
1005  RepoStatus newstatus = parser::plaindir::dirStatus( media.getPathName( info.path() ) );
1006 
1007  Pathname productpath( tmpdir.path() / info.path() );
1008  filesystem::assert_dir( productpath );
1009  std::ofstream file( (productpath/"cookie").c_str() );
1010  if ( !file )
1011  {
1012  // TranslatorExplanation '%s' is a filename
1013  ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), (productpath/"cookie").c_str() )));
1014  }
1015  file << url;
1016  if ( ! info.path().empty() && info.path() != "/" )
1017  file << " (" << info.path() << ")";
1018  file << endl;
1019  file << newstatus.checksum() << endl;
1020 
1021  file.close();
1022  }
1023  else
1024  {
1026  }
1027 
1028  // ok we have the metadata, now exchange
1029  // the contents
1030  filesystem::exchange( tmpdir.path(), mediarootpath );
1031 
1032  // we are done.
1033  return;
1034  }
1035  catch ( const Exception &e )
1036  {
1037  ZYPP_CAUGHT(e);
1038  ERR << "Trying another url..." << endl;
1039 
1040  // remember the exception caught for the *first URL*
1041  // if all other URLs fail, the rexception will be thrown with the
1042  // cause of the problem of the first URL remembered
1043  if (it == info.baseUrlsBegin())
1044  rexception.remember(e);
1045  }
1046  } // for every url
1047  ERR << "No more urls..." << endl;
1048  ZYPP_THROW(rexception);
1049  }
1050 
1052 
1054  const ProgressData::ReceiverFnc & progressfnc )
1055  {
1056  ProgressData progress(100);
1057  progress.sendTo(progressfnc);
1058 
1060  progress.toMax();
1061  }
1062 
1064  const ProgressData::ReceiverFnc & progressfnc )
1065  {
1066  ProgressData progress(100);
1067  progress.sendTo(progressfnc);
1068 
1070  progress.toMax();
1071  }
1072 
1074  CacheBuildPolicy policy,
1075  const ProgressData::ReceiverFnc & progressrcv )
1076  {
1077  assert_alias(info);
1078  Pathname mediarootpath = rawcache_path_for_repoinfo( _pimpl->options, info );
1079  Pathname productdatapath = rawproductdata_path_for_repoinfo( _pimpl->options, info );
1080 
1082  {
1083  Exception ex(str::form( _("Can't create %s"), _pimpl->options.repoCachePath.c_str()) );
1084  ZYPP_THROW(ex);
1085  }
1086  RepoStatus raw_metadata_status = metadataStatus(info);
1087  if ( raw_metadata_status.empty() )
1088  {
1089  /* if there is no cache at this point, we refresh the raw
1090  in case this is the first time - if it's !autorefresh,
1091  we may still refresh */
1092  refreshMetadata(info, RefreshIfNeeded, progressrcv );
1093  raw_metadata_status = metadataStatus(info);
1094  }
1095 
1096  bool needs_cleaning = false;
1097  if ( isCached( info ) )
1098  {
1099  MIL << info.alias() << " is already cached." << endl;
1100  RepoStatus cache_status = cacheStatus(info);
1101 
1102  if ( cache_status.checksum() == raw_metadata_status.checksum() )
1103  {
1104  MIL << info.alias() << " cache is up to date with metadata." << endl;
1105  if ( policy == BuildIfNeeded ) {
1106  return;
1107  }
1108  else {
1109  MIL << info.alias() << " cache rebuild is forced" << endl;
1110  }
1111  }
1112 
1113  needs_cleaning = true;
1114  }
1115 
1116  ProgressData progress(100);
1118  progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1119  progress.name(str::form(_("Building repository '%s' cache"), info.label().c_str()));
1120  progress.toMin();
1121 
1122  if (needs_cleaning)
1123  {
1124  cleanCache(info);
1125  }
1126 
1127  MIL << info.alias() << " building cache..." << info.type() << endl;
1128 
1129  Pathname base = solv_path_for_repoinfo( _pimpl->options, info);
1130 
1131  if( filesystem::assert_dir(base) )
1132  {
1133  Exception ex(str::form( _("Can't create %s"), base.c_str()) );
1134  ZYPP_THROW(ex);
1135  }
1136 
1137  if( ! PathInfo(base).userMayW() )
1138  {
1139  Exception ex(str::form( _("Can't create cache at %s - no writing permissions."), base.c_str()) );
1140  ZYPP_THROW(ex);
1141  }
1142  Pathname solvfile = base / "solv";
1143 
1144  // do we have type?
1145  repo::RepoType repokind = info.type();
1146 
1147  // if the type is unknown, try probing.
1148  switch ( repokind.toEnum() )
1149  {
1150  case RepoType::NONE_e:
1151  // unknown, probe the local metadata
1152  repokind = probe( productdatapath.asUrl() );
1153  break;
1154  default:
1155  break;
1156  }
1157 
1158  MIL << "repo type is " << repokind << endl;
1159 
1160  switch ( repokind.toEnum() )
1161  {
1162  case RepoType::RPMMD_e :
1163  case RepoType::YAST2_e :
1164  case RepoType::RPMPLAINDIR_e :
1165  {
1166  // Take care we unlink the solvfile on exception
1167  ManagedFile guard( solvfile, filesystem::unlink );
1168  scoped_ptr<MediaMounter> forPlainDirs;
1169 
1171  cmd.push_back( "repo2solv.sh" );
1172 
1173  // repo2solv expects -o as 1st arg!
1174  cmd.push_back( "-o" );
1175  cmd.push_back( solvfile.asString() );
1176 
1177  if ( repokind == RepoType::RPMPLAINDIR )
1178  {
1179  forPlainDirs.reset( new MediaMounter( *info.baseUrlsBegin() ) );
1180  // recusive for plaindir as 2nd arg!
1181  cmd.push_back( "-R" );
1182  // FIXME this does only work form dir: URLs
1183  cmd.push_back( forPlainDirs->getPathName( info.path() ).c_str() );
1184  }
1185  else
1186  cmd.push_back( productdatapath.asString() );
1187 
1189  std::string errdetail;
1190 
1191  for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1192  WAR << " " << output;
1193  if ( errdetail.empty() ) {
1194  errdetail = prog.command();
1195  errdetail += '\n';
1196  }
1197  errdetail += output;
1198  }
1199 
1200  int ret = prog.close();
1201  if ( ret != 0 )
1202  {
1203  RepoException ex(str::form( _("Failed to cache repo (%d)."), ret ));
1204  ex.remember( errdetail );
1205  ZYPP_THROW(ex);
1206  }
1207 
1208  // We keep it.
1209  guard.resetDispose();
1210  }
1211  break;
1212  default:
1213  ZYPP_THROW(RepoUnknownTypeException( _("Unhandled repository type") ));
1214  break;
1215  }
1216  // update timestamp and checksum
1217  setCacheStatus(info, raw_metadata_status);
1218  MIL << "Commit cache.." << endl;
1219  progress.toMax();
1220  }
1221 
1223 
1225  { return probe( url, Pathname() ); }
1226 
1227  repo::RepoType RepoManager::probe( const Url & url, const Pathname & path ) const
1228  {
1229  MIL << "going to probe the repo type at " << url << " (" << path << ")" << endl;
1230 
1231  if ( url.getScheme() == "dir" && ! PathInfo( url.getPathName()/path ).isDir() )
1232  {
1233  // Handle non existing local directory in advance, as
1234  // MediaSetAccess does not support it.
1235  MIL << "Probed type NONE (not exists) at " << url << " (" << path << ")" << endl;
1236  return repo::RepoType::NONE;
1237  }
1238 
1239  // prepare exception to be thrown if the type could not be determined
1240  // due to a media exception. We can't throw right away, because of some
1241  // problems with proxy servers returning an incorrect error
1242  // on ftp file-not-found(bnc #335906). Instead we'll check another types
1243  // before throwing.
1244 
1245  // TranslatorExplanation '%s' is an URL
1246  RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
1247  bool gotMediaException = false;
1248  try
1249  {
1250  MediaSetAccess access(url);
1251  try
1252  {
1253  if ( access.doesFileExist(path/"/repodata/repomd.xml") )
1254  {
1255  MIL << "Probed type RPMMD at " << url << " (" << path << ")" << endl;
1256  return repo::RepoType::RPMMD;
1257  }
1258  }
1259  catch ( const media::MediaException &e )
1260  {
1261  ZYPP_CAUGHT(e);
1262  DBG << "problem checking for repodata/repomd.xml file" << endl;
1263  enew.remember(e);
1264  gotMediaException = true;
1265  }
1266 
1267  try
1268  {
1269  if ( access.doesFileExist(path/"/content") )
1270  {
1271  MIL << "Probed type YAST2 at " << url << " (" << path << ")" << endl;
1272  return repo::RepoType::YAST2;
1273  }
1274  }
1275  catch ( const media::MediaException &e )
1276  {
1277  ZYPP_CAUGHT(e);
1278  DBG << "problem checking for content file" << endl;
1279  enew.remember(e);
1280  gotMediaException = true;
1281  }
1282 
1283  // if it is a non-downloading URL denoting a directory
1284  if ( ! url.schemeIsDownloading() )
1285  {
1286  MediaMounter media( url );
1287  if ( PathInfo(media.getPathName()/path).isDir() )
1288  {
1289  // allow empty dirs for now
1290  MIL << "Probed type RPMPLAINDIR at " << url << " (" << path << ")" << endl;
1292  }
1293  }
1294  }
1295  catch ( const Exception &e )
1296  {
1297  ZYPP_CAUGHT(e);
1298  // TranslatorExplanation '%s' is an URL
1299  Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
1300  enew.remember(e);
1301  ZYPP_THROW(enew);
1302  }
1303 
1304  if (gotMediaException)
1305  ZYPP_THROW(enew);
1306 
1307  MIL << "Probed type NONE at " << url << " (" << path << ")" << endl;
1308  return repo::RepoType::NONE;
1309  }
1310 
1312 
1314  {
1315  MIL << "Going to clean up garbage in cache dirs" << endl;
1316 
1317  ProgressData progress(300);
1318  progress.sendTo(progressrcv);
1319  progress.toMin();
1320 
1321  std::list<Pathname> cachedirs;
1322  cachedirs.push_back(_pimpl->options.repoRawCachePath);
1323  cachedirs.push_back(_pimpl->options.repoPackagesCachePath);
1324  cachedirs.push_back(_pimpl->options.repoSolvCachePath);
1325 
1326  for_( dir, cachedirs.begin(), cachedirs.end() )
1327  {
1328  if ( PathInfo(*dir).isExist() )
1329  {
1330  std::list<Pathname> entries;
1331  if ( filesystem::readdir( entries, *dir, false ) != 0 )
1332  // TranslatorExplanation '%s' is a pathname
1333  ZYPP_THROW(Exception(str::form(_("Failed to read directory '%s'"), dir->c_str())));
1334 
1335  unsigned sdircount = entries.size();
1336  unsigned sdircurrent = 1;
1337  for_( subdir, entries.begin(), entries.end() )
1338  {
1339  // if it does not belong known repo, make it disappear
1340  bool found = false;
1341  for_( r, repoBegin(), repoEnd() )
1342  if ( subdir->basename() == r->escaped_alias() )
1343  { found = true; break; }
1344 
1345  if ( ! found )
1346  filesystem::recursive_rmdir( *subdir );
1347 
1348  progress.set( progress.val() + sdircurrent * 100 / sdircount );
1349  ++sdircurrent;
1350  }
1351  }
1352  else
1353  progress.set( progress.val() + 100 );
1354  }
1355  progress.toMax();
1356  }
1357 
1359 
1361  const ProgressData::ReceiverFnc & progressrcv )
1362  {
1363  ProgressData progress(100);
1364  progress.sendTo(progressrcv);
1365  progress.toMin();
1366 
1367  MIL << "Removing raw metadata cache for " << info.alias() << endl;
1369 
1370  progress.toMax();
1371  }
1372 
1374 
1375  bool RepoManager::isCached( const RepoInfo &info ) const
1376  {
1377  return PathInfo(solv_path_for_repoinfo( _pimpl->options, info ) / "solv").isExist();
1378  }
1379 
1381  {
1382 
1383  Pathname cookiefile = solv_path_for_repoinfo(_pimpl->options, info) / "cookie";
1384 
1385  return RepoStatus::fromCookieFile(cookiefile);
1386  }
1387 
1388  void RepoManager::setCacheStatus( const RepoInfo &info, const RepoStatus &status )
1389  {
1390  Pathname base = solv_path_for_repoinfo(_pimpl->options, info);
1391  filesystem::assert_dir(base);
1392  Pathname cookiefile = base / "cookie";
1393 
1394  status.saveToCookieFile(cookiefile);
1395  }
1396 
1398  const ProgressData::ReceiverFnc & progressrcv )
1399  {
1400  assert_alias(info);
1401  Pathname solvfile = solv_path_for_repoinfo(_pimpl->options, info) / "solv";
1402 
1403  if ( ! PathInfo(solvfile).isExist() )
1405 
1406  sat::Pool::instance().reposErase( info.alias() );
1407  try
1408  {
1409  Repository repo = sat::Pool::instance().addRepoSolv( solvfile, info );
1410  // test toolversion in order to rebuild solv file in case
1411  // it was written by an old libsolv-tool parser.
1412  //
1413  // Known version strings used:
1414  // - <no string>
1415  // - "1.0"
1416  //
1418  if ( toolversion.begin().asString().empty() )
1419  {
1420  repo.eraseFromPool();
1421  ZYPP_THROW(Exception("Solv-file was created by old parser."));
1422  }
1423  // else: up-to-date (or even newer).
1424  }
1425  catch ( const Exception & exp )
1426  {
1427  ZYPP_CAUGHT( exp );
1428  MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1429  cleanCache( info, progressrcv );
1430  buildCache( info, BuildIfNeeded, progressrcv );
1431 
1432  sat::Pool::instance().addRepoSolv( solvfile, info );
1433  }
1434  }
1435 
1437 
1439  const ProgressData::ReceiverFnc & progressrcv )
1440  {
1441  assert_alias(info);
1442 
1443  ProgressData progress(100);
1445  progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1446  progress.name(str::form(_("Adding repository '%s'"), info.label().c_str()));
1447  progress.toMin();
1448 
1449  MIL << "Try adding repo " << info << endl;
1450 
1451  RepoInfo tosave = info;
1452  if(_pimpl->repos.find(tosave)!= _pimpl->repos.end())
1454 
1455  // check the first url for now
1456  if ( _pimpl->options.probe )
1457  {
1458  DBG << "unknown repository type, probing" << endl;
1459 
1460  RepoType probedtype;
1461  probedtype = probe( *tosave.baseUrlsBegin(), info.path() );
1462  if ( tosave.baseUrlsSize() > 0 )
1463  {
1464  if ( probedtype == RepoType::NONE )
1466  else
1467  tosave.setType(probedtype);
1468  }
1469  }
1470 
1471  progress.set(50);
1472 
1473  // assert the directory exists
1475 
1476  Pathname repofile = _pimpl->generateNonExistingName(
1478  // now we have a filename that does not exists
1479  MIL << "Saving repo in " << repofile << endl;
1480 
1481  std::ofstream file(repofile.c_str());
1482  if (!file)
1483  {
1484  // TranslatorExplanation '%s' is a filename
1485  ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1486  }
1487 
1488  tosave.dumpAsIniOn(file);
1489  tosave.setFilepath(repofile);
1490  tosave.setMetadataPath( metadataPath( tosave ) );
1491  tosave.setPackagesPath( packagesPath( tosave ) );
1492  {
1493  // We chould fix the API as we must injet those paths
1494  // into the repoinfo in order to keep it usable.
1495  RepoInfo & oinfo( const_cast<RepoInfo &>(info) );
1496  oinfo.setMetadataPath( metadataPath( tosave ) );
1497  oinfo.setPackagesPath( packagesPath( tosave ) );
1498  }
1499  _pimpl->repos.insert(tosave);
1500 
1501  progress.set(90);
1502 
1503  // check for credentials in Urls
1504  bool havePasswords = false;
1505  for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
1506  if ( urlit->hasCredentialsInAuthority() )
1507  {
1508  havePasswords = true;
1509  break;
1510  }
1511  // save the credentials
1512  if ( havePasswords )
1513  {
1516 
1517  for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
1518  if (urlit->hasCredentialsInAuthority())
1520  cm.saveInUser(media::AuthData(*urlit));
1521  }
1522 
1523  HistoryLog().addRepository(tosave);
1524 
1525  progress.toMax();
1526  MIL << "done" << endl;
1527  }
1528 
1530  const ProgressData::ReceiverFnc & progressrcv )
1531  {
1532  std::list<RepoInfo> repos = readRepoFile(url);
1533  for ( std::list<RepoInfo>::const_iterator it = repos.begin();
1534  it != repos.end();
1535  ++it )
1536  {
1537  // look if the alias is in the known repos.
1538  for_ ( kit, repoBegin(), repoEnd() )
1539  {
1540  if ( (*it).alias() == (*kit).alias() )
1541  {
1542  ERR << "To be added repo " << (*it).alias() << " conflicts with existing repo " << (*kit).alias() << endl;
1544  }
1545  }
1546  }
1547 
1548  std::string filename = Pathname(url.getPathName()).basename();
1549 
1550  if ( filename == Pathname() )
1551  {
1552  // TranslatorExplanation '%s' is an URL
1553  ZYPP_THROW(RepoException(str::form( _("Invalid repo file name at '%s'"), url.asString().c_str() )));
1554  }
1555 
1556  // assert the directory exists
1558 
1559  Pathname repofile = _pimpl->generateNonExistingName(_pimpl->options.knownReposPath, filename);
1560  // now we have a filename that does not exists
1561  MIL << "Saving " << repos.size() << " repo" << ( repos.size() ? "s" : "" ) << " in " << repofile << endl;
1562 
1563  std::ofstream file(repofile.c_str());
1564  if (!file)
1565  {
1566  // TranslatorExplanation '%s' is a filename
1567  ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), repofile.c_str() )));
1568  }
1569 
1570  for ( std::list<RepoInfo>::iterator it = repos.begin();
1571  it != repos.end();
1572  ++it )
1573  {
1574  MIL << "Saving " << (*it).alias() << endl;
1575  it->setFilepath(repofile.asString());
1576  it->dumpAsIniOn(file);
1577  _pimpl->repos.insert(*it);
1578 
1580  }
1581 
1582  MIL << "done" << endl;
1583  }
1584 
1586 
1588  const ProgressData::ReceiverFnc & progressrcv)
1589  {
1590  ProgressData progress;
1592  progress.sendTo( ProgressReportAdaptor( progressrcv, report ) );
1593  progress.name(str::form(_("Removing repository '%s'"), info.label().c_str()));
1594 
1595  MIL << "Going to delete repo " << info.alias() << endl;
1596 
1597  for_( it, repoBegin(), repoEnd() )
1598  {
1599  // they can be the same only if the provided is empty, that means
1600  // the provided repo has no alias
1601  // then skip
1602  if ( (!info.alias().empty()) && ( info.alias() != (*it).alias() ) )
1603  continue;
1604 
1605  // TODO match by url
1606 
1607  // we have a matcing repository, now we need to know
1608  // where it does come from.
1609  RepoInfo todelete = *it;
1610  if (todelete.filepath().empty())
1611  {
1612  ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1613  }
1614  else
1615  {
1616  // figure how many repos are there in the file:
1617  std::list<RepoInfo> filerepos = repositories_in_file(todelete.filepath());
1618  if ( (filerepos.size() == 1) && ( filerepos.front().alias() == todelete.alias() ) )
1619  {
1620  // easy, only this one, just delete the file
1621  if ( filesystem::unlink(todelete.filepath()) != 0 )
1622  {
1623  // TranslatorExplanation '%s' is a filename
1624  ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), todelete.filepath().c_str() )));
1625  }
1626  MIL << todelete.alias() << " sucessfully deleted." << endl;
1627  }
1628  else
1629  {
1630  // there are more repos in the same file
1631  // write them back except the deleted one.
1632  //TmpFile tmp;
1633  //std::ofstream file(tmp.path().c_str());
1634 
1635  // assert the directory exists
1636  filesystem::assert_dir(todelete.filepath().dirname());
1637 
1638  std::ofstream file(todelete.filepath().c_str());
1639  if (!file)
1640  {
1641  // TranslatorExplanation '%s' is a filename
1642  ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), todelete.filepath().c_str() )));
1643  }
1644  for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1645  fit != filerepos.end();
1646  ++fit )
1647  {
1648  if ( (*fit).alias() != todelete.alias() )
1649  (*fit).dumpAsIniOn(file);
1650  }
1651  }
1652 
1653  CombinedProgressData subprogrcv(progress, 70);
1654  CombinedProgressData cleansubprogrcv(progress, 30);
1655  // now delete it from cache
1656  if ( isCached(todelete) )
1657  cleanCache( todelete, subprogrcv);
1658  // now delete metadata (#301037)
1659  cleanMetadata( todelete, cleansubprogrcv);
1660  _pimpl->repos.erase(todelete);
1661  MIL << todelete.alias() << " sucessfully deleted." << endl;
1663  return;
1664  } // else filepath is empty
1665 
1666  }
1667  // should not be reached on a sucess workflow
1669  }
1670 
1672 
1673  void RepoManager::modifyRepository( const std::string &alias,
1674  const RepoInfo & newinfo_r,
1675  const ProgressData::ReceiverFnc & progressrcv )
1676  {
1677  RepoInfo toedit = getRepositoryInfo(alias);
1678  RepoInfo newinfo( newinfo_r ); // need writable copy to upadte housekeeping data
1679 
1680  // check if the new alias already exists when renaming the repo
1681  if ( alias != newinfo.alias() && hasRepo( newinfo.alias() ) )
1682  {
1684  }
1685 
1686  if (toedit.filepath().empty())
1687  {
1688  ZYPP_THROW(RepoException( _("Can't figure out where the repo is stored.") ));
1689  }
1690  else
1691  {
1692  // figure how many repos are there in the file:
1693  std::list<RepoInfo> filerepos = repositories_in_file(toedit.filepath());
1694 
1695  // there are more repos in the same file
1696  // write them back except the deleted one.
1697  //TmpFile tmp;
1698  //std::ofstream file(tmp.path().c_str());
1699 
1700  // assert the directory exists
1701  filesystem::assert_dir(toedit.filepath().dirname());
1702 
1703  std::ofstream file(toedit.filepath().c_str());
1704  if (!file)
1705  {
1706  // TranslatorExplanation '%s' is a filename
1707  ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), toedit.filepath().c_str() )));
1708  }
1709  for ( std::list<RepoInfo>::const_iterator fit = filerepos.begin();
1710  fit != filerepos.end();
1711  ++fit )
1712  {
1713  // if the alias is different, dump the original
1714  // if it is the same, dump the provided one
1715  if ( (*fit).alias() != toedit.alias() )
1716  (*fit).dumpAsIniOn(file);
1717  else
1718  newinfo.dumpAsIniOn(file);
1719  }
1720 
1721  newinfo.setFilepath(toedit.filepath());
1722  _pimpl->repos.erase(toedit);
1723  _pimpl->repos.insert(newinfo);
1724  HistoryLog(_pimpl->options.rootDir).modifyRepository(toedit, newinfo);
1725  MIL << "repo " << alias << " modified" << endl;
1726  }
1727  }
1728 
1730 
1731  RepoInfo RepoManager::getRepositoryInfo( const std::string &alias,
1732  const ProgressData::ReceiverFnc & progressrcv )
1733  {
1734  RepoInfo info;
1735  info.setAlias(alias);
1736  RepoConstIterator it = _pimpl->repos.find( info );
1737  if( it == repoEnd() )
1739  else
1740  return *it;
1741  }
1742 
1744 
1746  const url::ViewOption & urlview,
1747  const ProgressData::ReceiverFnc & progressrcv )
1748  {
1749  for_( it, repoBegin(), repoEnd() )
1750  {
1751  for(RepoInfo::urls_const_iterator urlit = (*it).baseUrlsBegin();
1752  urlit != (*it).baseUrlsEnd();
1753  ++urlit)
1754  {
1755  if ((*urlit).asString(urlview) == url.asString(urlview))
1756  return *it;
1757  }
1758  }
1759  RepoInfo info;
1760  info.setBaseUrl(url);
1762  }
1763 
1765  //
1766  // Services
1767  //
1769 
1771  { return _pimpl->services.empty(); }
1772 
1774  { return _pimpl->services.size(); }
1775 
1777  { return _pimpl->services.begin(); }
1778 
1780  { return _pimpl->services.end(); }
1781 
1782  ServiceInfo RepoManager::getService( const std::string & alias ) const
1783  {
1784  for_( it, serviceBegin(), serviceEnd() )
1785  if ( it->alias() == alias )
1786  return *it;
1787  return ServiceInfo::noService;
1788  }
1789 
1790  bool RepoManager::hasService( const std::string & alias ) const
1791  {
1792  for_( it, serviceBegin(), serviceEnd() )
1793  if ( it->alias() == alias )
1794  return true;
1795  return false;
1796  }
1797 
1799 
1800  void RepoManager::addService( const std::string & alias, const Url & url )
1801  {
1802  addService( ServiceInfo(alias, url) );
1803  }
1804 
1805  void RepoManager::addService( const ServiceInfo & service )
1806  {
1807  assert_alias( service );
1808 
1809  // check if service already exists
1810  if ( hasService( service.alias() ) )
1812 
1813  // Writable ServiceInfo is needed to save the location
1814  // of the .service file. Finaly insert into the service list.
1815  ServiceInfo toSave( service );
1816  _pimpl->saveService( toSave );
1817  _pimpl->services.insert( toSave );
1818 
1819  // check for credentials in Url (username:password, not ?credentials param)
1820  if ( toSave.url().hasCredentialsInAuthority() )
1821  {
1824 
1826  cm.saveInUser(media::AuthData(toSave.url()));
1827  }
1828 
1829  MIL << "added service " << toSave.alias() << endl;
1830  }
1831 
1833 
1834  void RepoManager::removeService( const std::string & alias )
1835  {
1836  MIL << "Going to delete repo " << alias << endl;
1837 
1838  const ServiceInfo & service = getService( alias );
1839 
1840  Pathname location = service.filepath();
1841  if( location.empty() )
1842  {
1843  ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
1844  }
1845 
1846  ServiceSet tmpSet;
1847  parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
1848 
1849  // only one service definition in the file
1850  if ( tmpSet.size() == 1 )
1851  {
1852  if ( filesystem::unlink(location) != 0 )
1853  {
1854  // TranslatorExplanation '%s' is a filename
1855  ZYPP_THROW(RepoException(str::form( _("Can't delete '%s'"), location.c_str() )));
1856  }
1857  MIL << alias << " sucessfully deleted." << endl;
1858  }
1859  else
1860  {
1861  filesystem::assert_dir(location.dirname());
1862 
1863  std::ofstream file(location.c_str());
1864  if( !file )
1865  {
1866  // TranslatorExplanation '%s' is a filename
1867  ZYPP_THROW( Exception(str::form( _("Can't open file '%s' for writing."), location.c_str() )));
1868  }
1869 
1870  for_(it, tmpSet.begin(), tmpSet.end())
1871  {
1872  if( it->alias() != alias )
1873  it->dumpAsIniOn(file);
1874  }
1875 
1876  MIL << alias << " sucessfully deleted from file " << location << endl;
1877  }
1878 
1879  // now remove all repositories added by this service
1880  RepoCollector rcollector;
1881  getRepositoriesInService( alias,
1882  boost::make_function_output_iterator(
1883  bind( &RepoCollector::collect, &rcollector, _1 ) ) );
1884  // cannot do this directly in getRepositoriesInService - would invalidate iterators
1885  for_(rit, rcollector.repos.begin(), rcollector.repos.end())
1886  removeRepository(*rit);
1887  }
1888 
1889  void RepoManager::removeService( const ServiceInfo & service )
1890  { removeService(service.alias()); }
1891 
1893 
1895  {
1896  // copy the set of services since refreshService
1897  // can eventually invalidate the iterator
1898  ServiceSet services( serviceBegin(), serviceEnd() );
1899  for_( it, services.begin(), services.end() )
1900  {
1901  if ( !it->enabled() )
1902  continue;
1903 
1904  try {
1905  refreshService(*it);
1906  }
1907  catch ( const repo::ServicePluginInformalException & e )
1908  { ;/* ignore ServicePluginInformalException */ }
1909  }
1910  }
1911 
1913  { refreshService( service.alias() ); }
1914 
1915  void RepoManager::refreshService( const std::string & alias )
1916  {
1917  ServiceInfo service( getService( alias ) );
1918  assert_alias( service );
1919  assert_url( service );
1920  // NOTE: It might be necessary to modify and rewrite the service info.
1921  // Either when probing the type, or when adjusting the repositories
1922  // enable/disable state.:
1923  bool serviceModified = false;
1924  MIL << "Going to refresh service '" << service.alias() << "', url: "<< service.url() << endl;
1925 
1927 
1928  // if the type is unknown, try probing.
1929  if ( service.type() == repo::ServiceType::NONE )
1930  {
1931  repo::ServiceType type = probeService( service.url() );
1932  if ( type != ServiceType::NONE )
1933  {
1934  service.setProbedType( type ); // lazy init!
1935  serviceModified = true;
1936  }
1937  }
1938 
1939  // get target distro identifier
1940  std::string servicesTargetDistro = _pimpl->options.servicesTargetDistro;
1941  if ( servicesTargetDistro.empty() )
1942  {
1943  servicesTargetDistro = Target::targetDistribution( Pathname() );
1944  }
1945  DBG << "ServicesTargetDistro: " << servicesTargetDistro << endl;
1946 
1947  // parse it
1948  RepoCollector collector(servicesTargetDistro);
1949  // FIXME Ugly hack: ServiceRepos may throw ServicePluginInformalException
1950  // which is actually a notification. Using an exception for this
1951  // instead of signal/callback is bad. Needs to be fixed here, in refreshServices()
1952  // and in zypper.
1953  std::pair<DefaultIntegral<bool,false>, repo::ServicePluginInformalException> uglyHack;
1954  try {
1955  ServiceRepos repos(service, bind( &RepoCollector::collect, &collector, _1 ));
1956  }
1957  catch ( const repo::ServicePluginInformalException & e )
1958  {
1959  /* ignore ServicePluginInformalException and throw later */
1960  uglyHack.first = true;
1961  uglyHack.second = e;
1962  }
1963 
1964  // set service alias and base url for all collected repositories
1965  for_( it, collector.repos.begin(), collector.repos.end() )
1966  {
1967  // if the repo url was not set by the repoindex parser, set service's url
1968  Url url;
1969 
1970  if ( it->baseUrlsEmpty() )
1971  url = service.url();
1972  else
1973  {
1974  // service repo can contain only one URL now, so no need to iterate.
1975  url = *it->baseUrlsBegin();
1976  }
1977 
1978  // libzypp currently has problem with separate url + path handling
1979  // so just append the path to the baseurl
1980  if ( !it->path().empty() )
1981  {
1982  Pathname path(url.getPathName());
1983  path /= it->path();
1984  url.setPathName( path.asString() );
1985  it->setPath("");
1986  }
1987 
1988  // Prepend service alias:
1989  it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
1990 
1991  // save the url
1992  it->setBaseUrl( url );
1993  // set refrence to the parent service
1994  it->setService( service.alias() );
1995  }
1996 
1998  // Now compare collected repos with the ones in the system...
1999  //
2000  RepoInfoList oldRepos;
2001  getRepositoriesInService( service.alias(), std::back_inserter( oldRepos ) );
2002 
2003  // find old repositories to remove...
2004  for_( it, oldRepos.begin(), oldRepos.end() )
2005  {
2006  if ( ! foundAliasIn( it->alias(), collector.repos ) )
2007  {
2008  if ( it->enabled() && ! service.repoToDisableFind( it->alias() ) )
2009  {
2010  DBG << "Service removes enabled repo " << it->alias() << endl;
2011  service.addRepoToEnable( it->alias() );
2012  serviceModified = true;
2013  }
2014  else
2015  {
2016  DBG << "Service removes disabled repo " << it->alias() << endl;
2017  }
2018  removeRepository( *it );
2019  }
2020  }
2021 
2023  // create missing repositories and modify exising ones if needed...
2024  for_( it, collector.repos.begin(), collector.repos.end() )
2025  {
2026  // Service explicitly requests the repo being enabled?
2027  // Service explicitly requests the repo being disabled?
2028  // And hopefully not both ;) If so, enable wins.
2029  bool beEnabled = service.repoToEnableFind( it->alias() );
2030  bool beDisabled = service.repoToDisableFind( it->alias() );
2031 
2032  // Make sure the service repo is created with the
2033  // appropriate enable
2034  if ( beEnabled ) it->setEnabled(true);
2035  if ( beDisabled ) it->setEnabled(false);
2036 
2037  if ( beEnabled )
2038  {
2039  // Remove from enable request list.
2040  // NOTE: repoToDisable is handled differently.
2041  // It gets cleared on each refresh.
2042  service.delRepoToEnable( it->alias() );
2043  serviceModified = true;
2044  }
2045 
2046  RepoInfoList::iterator oldRepo( findAlias( it->alias(), oldRepos ) );
2047  if ( oldRepo == oldRepos.end() )
2048  {
2049  // Not found in oldRepos ==> a new repo to add
2050 
2051  // At that point check whether a repo with the same alias
2052  // exists outside this service. Maybe forcefully re-alias
2053  // the existing repo?
2054  DBG << "Service adds repo " << it->alias() << " " << (it->enabled()?"enabled":"disabled") << endl;
2055  addRepository( *it );
2056 
2057  // save repo credentials
2058  // ma@: task for modifyRepository?
2059  }
2060  else
2061  {
2062  // ==> an exising repo to check
2063  bool oldRepoModified = false;
2064 
2065  // changed enable?
2066  if ( beEnabled )
2067  {
2068  if ( ! oldRepo->enabled() )
2069  {
2070  DBG << "Service repo " << it->alias() << " gets enabled" << endl;
2071  oldRepo->setEnabled( true );
2072  oldRepoModified = true;
2073  }
2074  else
2075  {
2076  DBG << "Service repo " << it->alias() << " stays enabled" << endl;
2077  }
2078  }
2079  else if ( beDisabled )
2080  {
2081  if ( oldRepo->enabled() )
2082  {
2083  DBG << "Service repo " << it->alias() << " gets disabled" << endl;
2084  oldRepo->setEnabled( false );
2085  oldRepoModified = true;
2086  }
2087  else
2088  {
2089  DBG << "Service repo " << it->alias() << " stays disabled" << endl;
2090  }
2091  }
2092  else
2093  {
2094  DBG << "Service repo " << it->alias() << " stays " << (oldRepo->enabled()?"enabled":"disabled") << endl;
2095  }
2096 
2097  // changed url?
2098  // service repo can contain only one URL now, so no need to iterate.
2099  if ( oldRepo->url() != it->url() )
2100  {
2101  DBG << "Service repo " << it->alias() << " gets new URL " << it->url() << endl;
2102  oldRepo->setBaseUrl( it->url() );
2103  oldRepoModified = true;
2104  }
2105 
2106  // save if modified:
2107  if ( oldRepoModified )
2108  {
2109  modifyRepository( oldRepo->alias(), *oldRepo );
2110  }
2111  }
2112  }
2113 
2114  // Unlike reposToEnable, reposToDisable is always cleared after refresh.
2115  if ( ! service.reposToDisableEmpty() )
2116  {
2117  service.clearReposToDisable();
2118  serviceModified = true;
2119  }
2120 
2122  // save service if modified:
2123  if ( serviceModified )
2124  {
2125  // write out modified service file.
2126  modifyService( service.alias(), service );
2127  }
2128 
2129  if ( uglyHack.first )
2130  {
2131  throw( uglyHack.second ); // intentionally not ZYPP_THROW
2132  }
2133  }
2134 
2136 
2137  void RepoManager::modifyService(const std::string & oldAlias, const ServiceInfo & newService)
2138  {
2139  MIL << "Going to modify service " << oldAlias << endl;
2140 
2141  // we need a writable copy to link it to the file where
2142  // it is saved if we modify it
2143  ServiceInfo service(newService);
2144 
2145  if ( service.type() == ServiceType::PLUGIN )
2146  {
2147  MIL << "Not modifying plugin service '" << oldAlias << "'" << endl;
2148  return;
2149  }
2150 
2151  const ServiceInfo & oldService = getService(oldAlias);
2152 
2153  Pathname location = oldService.filepath();
2154  if( location.empty() )
2155  {
2156  ZYPP_THROW(RepoException( _("Can't figure out where the service is stored.") ));
2157  }
2158 
2159  // remember: there may multiple services being defined in one file:
2160  ServiceSet tmpSet;
2161  parser::ServiceFileReader( location, ServiceCollector(tmpSet) );
2162 
2163  filesystem::assert_dir(location.dirname());
2164  std::ofstream file(location.c_str());
2165  for_(it, tmpSet.begin(), tmpSet.end())
2166  {
2167  if( *it != oldAlias )
2168  it->dumpAsIniOn(file);
2169  }
2170  service.dumpAsIniOn(file);
2171  file.close();
2172  service.setFilepath(location);
2173 
2174  _pimpl->services.erase(oldAlias);
2175  _pimpl->services.insert(service);
2176 
2177  // changed properties affecting also repositories
2178  if( oldAlias != service.alias() // changed alias
2179  || oldService.enabled() != service.enabled() // changed enabled status
2180  )
2181  {
2182  std::vector<RepoInfo> toModify;
2183  getRepositoriesInService(oldAlias, std::back_inserter(toModify));
2184  for_( it, toModify.begin(), toModify.end() )
2185  {
2186  if (oldService.enabled() && !service.enabled())
2187  it->setEnabled(false);
2188  else if (!oldService.enabled() && service.enabled())
2189  {
2192  }
2193  else
2194  it->setService(service.alias());
2195  modifyRepository(it->alias(), *it);
2196  }
2197  }
2198 
2200  }
2201 
2203 
2205  {
2206  try
2207  {
2208  MediaSetAccess access(url);
2209  if ( access.doesFileExist("/repo/repoindex.xml") )
2210  return repo::ServiceType::RIS;
2211  }
2212  catch ( const media::MediaException &e )
2213  {
2214  ZYPP_CAUGHT(e);
2215  // TranslatorExplanation '%s' is an URL
2216  RepoException enew(str::form( _("Error trying to read from '%s'"), url.asString().c_str() ));
2217  enew.remember(e);
2218  ZYPP_THROW(enew);
2219  }
2220  catch ( const Exception &e )
2221  {
2222  ZYPP_CAUGHT(e);
2223  // TranslatorExplanation '%s' is an URL
2224  Exception enew(str::form( _("Unknown error reading from '%s'"), url.asString().c_str() ));
2225  enew.remember(e);
2226  ZYPP_THROW(enew);
2227  }
2228 
2229  return repo::ServiceType::NONE;
2230  }
2231 
2233 
2234  std::ostream & operator<<( std::ostream & str, const RepoManager & obj )
2235  {
2236  return str << *obj._pimpl;
2237  }
2238 
2240 } // namespace zypp