Imported Upstream version 14.45.0
[platform/upstream/libzypp.git] / zypp / ui / SelectableImpl.cc
index a01221f..ebcfc31 100644 (file)
@@ -23,11 +23,107 @@ namespace zypp
   namespace ui
   { /////////////////////////////////////////////////////////////////
 
+    /** Simple ResStatus backup stack.
+     * \ref restore simply rewinds all remembered status.
+    */
+    class StatusBackup
+    {
+      public:
+        typedef ResStatus::TransactByValue Causer;
+
+      public:
+        /** Backup status. */
+        ResStatus & backup( ResStatus & status_r )
+        {
+          _backup.push_back( status_r );
+          return status_r;
+        }
+        /** \overload */
+        ResStatus & backup( const PoolItem & pi_r )
+        { return backup( pi_r.status() ); }
+
+        /** Backup status. */
+        ResStatus & operator()( ResStatus & status_r )
+        { return backup( status_r ); }
+        /** \overload */
+        ResStatus & operator()( const PoolItem & pi_r )
+        { return backup( pi_r.status() ); }
+
+        /** Restore all status. */
+        bool restore()
+        {
+          for_( rit, _backup.rbegin(), _backup.rend() )
+            rit->replay();
+          return false; // restore is done on error - return restore();
+        }
+
+      public:
+        /** lowlevel \c ResStatus::setTransact */
+        bool setTransact( const PoolItem & pi_r, bool yesno_r, Causer causer_r )
+        { return backup( pi_r ).setTransact( yesno_r, causer_r ); }
+
+        /** lowlevel \c ResStatus::setLock */
+        bool setLock( const PoolItem & pi_r, bool yesno_r, Causer causer_r )
+        { return backup( pi_r ).setLock( yesno_r, causer_r ); }
+
+        /** lowlevel \c ResStatus::setTransact(true). */
+        bool setTransactTrue( const PoolItem & pi_r, Causer causer_r )
+        { return setTransact( pi_r, true, causer_r ); }
+
+        /** lowlevel \c ResStatus::setTransact(false). */
+        bool setTransactFalse( const PoolItem & pi_r, Causer causer_r )
+        { return setTransact( pi_r, false, causer_r ); }
+
+      public:
+        /** highevel set transact (force unlock). */
+        bool transact( const PoolItem & pi_r, Causer causer_r )
+        {
+          ResStatus & status( backup( pi_r ) );
+          if ( ! status.setLock( false, causer_r ) ) return false;
+          if ( ! status.setTransact( true, causer_r ) ) return false;
+          return true;
+        }
+
+        /** highlevel set locked. */
+        bool lock( const PoolItem & pi_r, Causer causer_r )
+        {
+          ResStatus & status( backup( pi_r ) );
+          if ( ! status.setTransact( false, causer_r ) ) return false;
+          if ( ! status.setLock( true, causer_r ) ) return false;
+          return true;
+        }
+
+        /** highlevel unlock (also unsets transact). */
+        bool unlock( const PoolItem & pi_r, Causer causer_r )
+        {
+          ResStatus & status( backup( pi_r ) );
+          if ( ! status.setTransact( false, causer_r ) ) return false;
+          if ( ! status.setLock( false, causer_r ) ) return false;
+          return true;
+        }
+
+        /** Highlevel action. */
+        typedef bool (StatusBackup::*Action)( const PoolItem &, Causer );
+
+        /** Highlevel action on range of items. */
+        template <class _Iter>
+        bool forEach( _Iter begin_r, _Iter end_r, Action action_r, Causer causer_r )
+        {
+          for_( it, begin_r, end_r )
+            if ( ! (this->*action_r)( *it, causer_r ) )
+              return false;
+          return true;
+        }
+
+      private:
+        std::vector<resstatus::StatusBackup> _backup;
+    };
+
     ///////////////////////////////////////////////////////////////////
     //
     // CLASS NAME : StatusHelper
     //
-    /**
+    /** \todo Unify status and pickStatus.
     */
     struct StatusHelper
     {
@@ -35,6 +131,7 @@ namespace zypp
       : _impl( impl )
       , inst( impl.installedObj() )
       , cand( impl.candidateObj() )
+      , causer( causer_r )
       {}
 
       typedef Selectable::Impl::available_const_iterator available_const_iterator;
@@ -43,10 +140,10 @@ namespace zypp
       // Queries
       //
       bool hasInstalled() const
-      { return inst; }
+      { return bool(inst); }
 
       bool hasCandidate() const
-      { return cand; }
+      { return bool(cand); }
 
       bool hasInstalledOnly() const
       { return inst && !cand; }
@@ -57,103 +154,140 @@ namespace zypp
       bool hasBoth() const
       { return inst && cand; }
 
-      //
-      // ResStatus manip
-      //
-      void resetTransactingCandidates() const
+      /** \name Topevel methods must restore status on failure. */
+      //@{
+      bool setInstall()
       {
-        for ( available_const_iterator it = _impl.availableBegin();
-              it != _impl.availableEnd(); ++it )
-          {
-            (*it).status().setTransact( false, ResStatus::USER );
+        if ( cand )
+        {
+          if ( inst ) {
+            for_( it, _impl.installedBegin(), _impl.installedEnd() )
+            {
+              ResStatus & inststatus( backup( it->status() ) );
+              if ( ! inststatus.setTransact( false, causer ) ) return restore();
+              if ( ! inststatus.setLock    ( false, causer ) ) return restore();
+              if ( ! cand->multiversionInstall() )
+              {
+              // This is what the solver most probabely will do.
+              // If we are wrong the solver will correct it. But
+              // this way we will get a better disk usage result,
+              // even if no autosolving is on.
+                inststatus.setTransact( true, ResStatus::SOLVER );
+              }
+            }
           }
+          if ( ! unlockCandidates() ) return restore();
+          ResStatus & candstatus( backup( cand.status() ) );
+          if ( ! candstatus.setTransact( true, causer ) ) return restore();
+          return true;
+        }
+        return false;
       }
-      void unlockCandidates() const
+
+      bool setDelete()
       {
-        for ( available_const_iterator it = _impl.availableBegin();
-              it != _impl.availableEnd(); ++it )
+        if ( inst )
+        {
+          if ( ! resetTransactingCandidates() ) return restore();
+          for_( it, _impl.installedBegin(), _impl.installedEnd() )
           {
-            (*it).status().setTransact( false, ResStatus::USER );
-            (*it).status().setLock( false, ResStatus::USER );
+            ResStatus & inststatus( backup( it->status() ) );
+            if ( ! inststatus.setLock( false, causer ) ) return restore();
+            if ( ! inststatus.setTransact( true, causer ) ) return restore();
           }
+          return true;
+        }
+        return false;
       }
-      void lockCandidates() const
+
+      bool unset()
       {
-        for ( available_const_iterator it = _impl.availableBegin();
-              it != _impl.availableEnd(); ++it )
+        if ( inst )
+        {
+          for_( it, _impl.installedBegin(), _impl.installedEnd() )
           {
-            (*it).status().setTransact( false, ResStatus::USER );
-            (*it).status().setLock( true, ResStatus::USER );
+            ResStatus & inststatus( backup( it->status() ) );
+            if ( ! inststatus.setTransact( false, causer ) ) return restore();
+            if ( ! inststatus.setLock( false, causer ) ) return restore();
           }
+        }
+        if ( ! unlockCandidates() ) return restore();
+        return true;
       }
 
-      bool setInstall() const
+      bool setProtected()
       {
-        if ( cand )
+        if ( causer != ResStatus::USER ) // by user only
+          return false;
+
+        if ( inst ) {
+          resetTransactingCandidates();
+          for_( it, _impl.installedBegin(), _impl.installedEnd() )
           {
-             if ( inst ) {
-                 inst.status().setTransact( false, ResStatus::USER );
-                 inst.status().setLock    ( false, ResStatus::USER );
-                  if ( ! cand->installOnly() )
-                  {
-                    // This is what the solver most probabely will do.
-                    // If we are wrong the solver will correct it. But
-                    // this way we will get a better disk usage result,
-                    // even if no autosolving is on.
-                    inst.status().setTransact( true,  ResStatus::SOLVER );
-                  }
-             }
-              unlockCandidates();
-             return cand.status().setTransact( true, ResStatus::USER );
+            it->status().setTransact( false, causer );
+            it->status().setLock( true, causer );
           }
-        return false;
+          return true;
+        } else
+          return false;
       }
 
-      bool setDelete() const
+      bool setTaboo()
       {
-        if ( inst )
-          {
-            resetTransactingCandidates();
-           inst.status().setLock( false, ResStatus::USER );
-            return inst.status().setTransact( true, ResStatus::USER );
-          }
-        return false;
+        if ( causer != ResStatus::USER ) // by user only
+          return false;
+
+        if ( cand ) {
+          lockCandidates();
+          return true;
+        } else
+          return false;
       }
+      //@}
 
-      bool unset() const
+    private:
+      /** \name Helper methods backup status but do not replay. */
+      //@{
+      bool resetTransactingCandidates()
       {
-         if ( inst ) {
-             inst.status().setTransact( false, ResStatus::USER );
-             inst.status().setLock( false, ResStatus::USER );
-         }
-          unlockCandidates();
-         return true;
+        for_( it, _impl.availableBegin(), _impl.availableEnd() )
+        {
+          ResStatus & status( backup( (*it).status() ) );
+          if ( ! status.setTransact( false, causer ) ) return false;
+        }
+        return true;
       }
-
-      bool setProtected() const
+      bool unlockCandidates()
       {
-         if ( inst ) {
-              resetTransactingCandidates();
-             inst.status().setTransact( false, ResStatus::USER );
-             return inst.status().setLock( true, ResStatus::USER );
-         } else
-             return false;
+        for_( it, _impl.availableBegin(), _impl.availableEnd() )
+        {
+          ResStatus & status( backup( (*it).status() ) );
+          if ( ! status.setTransact( false, causer ) ) return false;
+          if ( ! status.setLock( false, causer ) ) return false;
+        }
+        return true;
       }
-
-      bool setTaboo() const
+      bool lockCandidates()
       {
-         if ( cand ) {
-              lockCandidates();
-             return true;
-         } else
-             return false;
+        for_( it, _impl.availableBegin(), _impl.availableEnd() )
+        {
+          ResStatus & status( backup( (*it).status() ) );
+          if ( ! status.setTransact( false, causer ) ) return false;
+          if ( ! status.setLock( true, causer ) ) return false;
+        }
+        return true;
       }
+      //@}
 
-
-    public:
+    private:
       const Selectable::Impl & _impl;
-      PoolItem inst;
-      PoolItem cand;
+      PoolItem                   inst;
+      PoolItem                   cand;
+      ResStatus::TransactByValue causer;
+
+    private:
+      bool restore() { return backup.restore(); }
+      StatusBackup backup;
     };
     ///////////////////////////////////////////////////////////////////
 
@@ -179,7 +313,7 @@ namespace zypp
           return( installedObj().status().isByUser() ? S_Del : S_AutoDel );
         }
 
-      if ( installedObj() && installedObj().status().isLocked() )
+      if ( installedObj() && allInstalledLocked() )
          return S_Protected;
 
       if ( !installedObj() && allCandidatesLocked() )
@@ -196,7 +330,7 @@ namespace zypp
       return S_NoInst;
     }
 
-    bool Selectable::Impl::setStatus( const Status state_r, ResStatus::TransactByValue causer_r )
+    bool Selectable::Impl::setStatus( Status state_r, ResStatus::TransactByValue causer_r )
     {
       StatusHelper self( *this, causer_r );
 
@@ -237,37 +371,230 @@ namespace zypp
       return false;
     }
 
-    PoolItem Selectable::Impl::setCandidate( ResObject::constPtr byUser_r )
+    PoolItem Selectable::Impl::setCandidate( const PoolItem & newCandidate_r, ResStatus::TransactByValue causer_r )
     {
-      _candidate = PoolItem();
+      PoolItem newCandidate;
 
-      if ( byUser_r ) // must be in available list
+      if ( newCandidate_r ) // must be in available list
+      {
+        for_( it, availableBegin(), availableEnd() )
         {
-          for ( available_const_iterator it = availableBegin();
-                it != availableEnd(); ++it )
-            {
-              if ( it->resolvable() == byUser_r )
-                {
-                  _candidate = *it;
-                  break;
-                }
-            }
+          if ( *it == newCandidate_r )
+          {
+            newCandidate = *it;
+            break;
+          }
         }
+      }
 
-      if ( _candidate )
+      if ( newCandidate )
+      {
+        PoolItem trans( transactingCandidate() );
+        if ( trans && trans != newCandidate )
         {
-          PoolItem trans( transactingCandidate() );
-          if ( trans && trans != _candidate )
-            {
-              // adjust transact to the new cancidate
-              trans.status().setTransact( false, ResStatus::USER );
-              _candidate.status().setTransact( true, ResStatus::USER );
-            }
+          // adjust transact to the new cancidate
+          if (    trans.status().maySetTransact( false, causer_r )
+               && newCandidate.status().maySetTransact( true, causer_r ) )
+          {
+            trans.status().setTransact( false, causer_r );
+            newCandidate.status().setTransact( true, causer_r );
+          }
+          else
+          {
+            // No permission to change a transacting candidate.
+            // Leave _candidate untouched and return NULL.
+            return PoolItem();
+          }
+        }
+      }
+
+      return _candidate = newCandidate;
+    }
+
+    ///////////////////////////////////////////////////////////////////
+
+    bool Selectable::Impl::pickInstall( const PoolItem & pi_r, ResStatus::TransactByValue causer_r, bool yesno_r )
+    {
+      if ( identicalInstalled( pi_r ) )
+        return setPickStatus( pi_r, ( yesno_r ? S_Update : S_KeepInstalled ), causer_r );
+      return setPickStatus( pi_r, ( yesno_r ? S_Install : S_NoInst ), causer_r );
+    }
+
+    bool Selectable::Impl::pickDelete( const PoolItem & pi_r, ResStatus::TransactByValue causer_r, bool yesno_r )
+    {
+      return setPickStatus( pi_r, ( yesno_r ? S_Del : S_KeepInstalled ), causer_r );
+    }
+
+    bool Selectable::Impl::setPickStatus( const PoolItem & pi_r, Status state_r, ResStatus::TransactByValue causer_r )
+    {
+      if ( pi_r.satSolvable().ident() != ident() )
+        return false;  // not my PoolItem
+
+      if ( ! multiversionInstall() )
+        return false;  // We're not yet ready for this.
+      // TODO: Without multiversionInstall take care at most ONE available is set
+      // to install. Upon install ALL installed get deleted. Only upon deletetion
+      // one might pick individual versions (but more than one would be an error here).
+
+      StatusBackup backup;
+      std::vector<PoolItem> i;
+      std::vector<PoolItem> a;
+
+      for_( it, installedBegin(), installedEnd() )
+        if ( identical( *it, pi_r ) )
+          i.push_back( *it );
+      for_( it, availableBegin(), availableEnd() )
+        if ( identical( *it, pi_r ) )
+          a.push_back( *it );
+
+      switch ( state_r )
+      {
+        case S_Protected:
+          if ( causer_r == ResStatus::USER && ! i.empty() )
+          {
+            if ( ! backup.forEach( i.begin(), i.end(), &StatusBackup::lock, causer_r ) ) return backup.restore();
+            if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::setTransactFalse, causer_r ) ) return backup.restore();
+            return true;
+          }
+          break;
+
+        case S_Taboo:
+          if ( causer_r == ResStatus::USER && ! a.empty() )
+          {
+            if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::lock, causer_r ) ) return backup.restore();
+            return true;
+          }
+          break;
+
+        case S_AutoDel:
+        case S_AutoInstall:
+        case S_AutoUpdate:
+          // Auto level is SOLVER level. UI may query, but not
+          // set at this level.
+          break;
+
+        case S_Del:
+          if ( ! i.empty() )
+          {
+            if ( ! backup.forEach( i.begin(), i.end(), &StatusBackup::transact, causer_r ) ) return backup.restore();
+            if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::setTransactFalse, causer_r ) ) return backup.restore();
+            return true;
+          }
+          break;
+
+        case S_Install:
+          if ( i.empty() && ! a.empty() )
+          {
+            // maybe unlock candidate only?
+            if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+            const PoolItem & cand( pi_r.status().isInstalled() ? *a.begin() : pi_r ); // status already backed up above
+            if ( ! cand.status().setTransact( true, causer_r ) ) return backup.restore();
+            return true;
+          }
+          break;
+
+        case S_Update:
+          if ( ! i.empty() && ! a.empty() )
+          {
+            if ( ! backup.forEach( i.begin(), i.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+            if ( ! backup.forEach( i.begin(), i.end(), &StatusBackup::setTransactTrue, ResStatus::SOLVER ) ) return backup.restore();
+            // maybe unlock candidate only?
+            if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+            const PoolItem & cand( pi_r.status().isInstalled() ? *a.begin() : pi_r ); // status already backed up above
+            if ( ! cand.status().setTransact( true, causer_r ) ) return backup.restore();
+            return true;
+          }
+          break;
+
+        case S_KeepInstalled:
+          if ( ! i.empty()  )
+          {
+            if ( ! backup.forEach( i.begin(), i.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+            if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+            return true;
+          }
+          break;
+
+        case S_NoInst:
+          if ( i.empty()  )
+          {
+            if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+            return true;
+          }
+          break;
+      }
+      return false;
+    }
+
+    Status Selectable::Impl::pickStatus( const PoolItem & pi_r ) const
+    {
+      if ( pi_r.satSolvable().ident() != ident() )
+        return Status(-1); // not my PoolItem
+
+      std::vector<PoolItem> i;
+      std::vector<PoolItem> a;
+      PoolItem ti;
+      PoolItem ta;
+
+      for_( it, installedBegin(), installedEnd() )
+        if ( identical( *it, pi_r ) )
+        {
+          i.push_back( *it );
+          if ( ! ti && it->status().transacts() )
+            ti = *it;
         }
 
-      return _candidate;
+      for_( it, availableBegin(), availableEnd() )
+        if ( identical( *it, pi_r ) )
+        {
+          a.push_back( *it );
+          if ( ! ta && it->status().transacts() )
+            ta = *it;
+        }
+
+      if ( ta )
+      {
+        if ( ta.status().isByUser() )
+          return( i.empty() ? S_Install : S_Update );
+        else
+          return( i.empty() ? S_AutoInstall : S_AutoUpdate );
+      }
+
+      if ( ti )
+      {
+        return( ti.status().isByUser() ? S_Del : S_AutoDel );
+      }
+
+      for_( it, i.begin(), i.end() )
+        if ( it->status().isLocked() )
+          return S_Protected;
+
+      if ( i.empty() )
+      {
+        bool allALocked = true;
+        for_( it, a.begin(), a.end() )
+          if ( ! it->status().isLocked() )
+          {
+            allALocked = false;
+            break;
+          }
+        if ( allALocked )
+          return S_Taboo;
+      }
+
+      // KEEP state:
+      if ( ! i.empty() )
+        return S_KeepInstalled;
+      // Report pseudo installed items as installed, if they are satisfied.
+      if ( traits::isPseudoInstalled( kind() )
+           && ( ta ? ta : *a.begin() ).status().isSatisfied() ) // no installed, so we must have candidate
+        return S_KeepInstalled;
+
+      return S_NoInst;
     }
 
+    ///////////////////////////////////////////////////////////////////
+
     ResStatus::TransactByValue Selectable::Impl::modifiedBy() const
     {
       PoolItem cand( candidateObj() );