Imported Upstream version 16.3.2
[platform/upstream/libzypp.git] / zypp / ui / SelectableImpl.cc
index f0c23f7..5d2a9d6 100644 (file)
@@ -23,29 +23,149 @@ 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 remove transact from non-multiversion packages. */
+        bool unsetNonMultiTransact(  const PoolItem & pi_r, Causer causer_r )
+       {
+         ResStatus & status( backup( pi_r ) );
+         if ( status.transacts() && ! pi_r.multiversionInstall() )
+         {
+           if ( ! status.setTransact( false, causer_r ) ) return false;
+         }
+         return true;
+       }
+
+        /** highlevel remove transact from multiversion packages. */
+        bool unsetMultiTransact(  const PoolItem & pi_r, Causer causer_r )
+       {
+         ResStatus & status( backup( pi_r ) );
+         if ( status.transacts() && pi_r.multiversionInstall() )
+         {
+           if ( ! status.setTransact( false, causer_r ) ) return false;
+         }
+         return true;
+       }
+
+        /** Highlevel action. */
+        typedef bool (StatusBackup::*Action)( const PoolItem &, Causer );
+
+        /** Highlevel action on range of items. */
+        template <class TIter>
+        bool forEach( TIter begin_r, TIter 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 Support non USER level mipulation.
+    /** \todo Unify status and pickStatus.
     */
     struct StatusHelper
     {
-      StatusHelper( const Selectable::Impl & impl )
+      StatusHelper( const Selectable::Impl & impl, ResStatus::TransactByValue causer_r )
       : _impl( impl )
       , inst( impl.installedObj() )
       , cand( impl.candidateObj() )
+      , causer( causer_r )
       {}
 
+      typedef Selectable::Impl::available_const_iterator available_const_iterator;
+
       //
       // Queries
       //
       bool hasInstalled() const
-      { return inst; }
+      { return bool(inst); }
 
       bool hasCandidate() const
-      { return cand; }
+      { return bool(cand); }
 
       bool hasInstalledOnly() const
       { return inst && !cand; }
@@ -56,72 +176,140 @@ namespace zypp
       bool hasBoth() const
       { return inst && cand; }
 
-      //
-      // ResStatus manip
-      //
-      /** \todo fix it, handle avaialable list */
-      bool setInstall( ResStatus::TransactByValue by_r ) const
+      /** \name Topevel methods must restore status on failure. */
+      //@{
+      bool setInstall()
       {
         if ( cand )
-          {
-             if ( inst ) {
-                 inst.status().setTransact( false, by_r );
-                 inst.status().setLock( false, by_r );
-             }
-             cand.status().setLock( false, by_r );
-             return cand.status().setTransact( true, by_r );
+        {
+          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;
       }
 
-      bool setDelete( ResStatus::TransactByValue by_r ) const
+      bool setDelete()
       {
         if ( inst )
+        {
+          if ( ! resetTransactingCandidates() ) return restore();
+          for_( it, _impl.installedBegin(), _impl.installedEnd() )
           {
-            if ( cand )
-              cand.status().setTransact( false, by_r );
-           inst.status().setLock( false, by_r );
-            return inst.status().setTransact( true, by_r );
+            ResStatus & inststatus( backup( it->status() ) );
+            if ( ! inststatus.setLock( false, causer ) ) return restore();
+            if ( ! inststatus.setTransact( true, causer ) ) return restore();
           }
+          return true;
+        }
         return false;
       }
 
-      bool unset( ResStatus::TransactByValue by_r ) const
+      bool unset()
       {
-         if ( inst ) {
-             inst.status().setTransact( false, by_r );
-             inst.status().setLock( false, by_r );
-         }
-         if ( cand ) {
-             cand.status().setTransact( false, by_r );
-             cand.status().setLock( false, by_r );
-         }
-         return true;
+        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 ( ! unlockCandidates() ) return restore();
+        return true;
       }
 
-      bool setProtected( ResStatus::TransactByValue by_r ) const
+      bool setProtected()
       {
-         if ( inst ) {
-             inst.status().setTransact( false, by_r );
-             return inst.status().setLock( true, by_r );
-         } else
-             return false;
+        if ( causer != ResStatus::USER ) // by user only
+          return false;
+
+        if ( inst ) {
+          resetTransactingCandidates();
+          for_( it, _impl.installedBegin(), _impl.installedEnd() )
+          {
+            it->status().setTransact( false, causer );
+            it->status().setLock( true, causer );
+          }
+          return true;
+        } else
+          return false;
       }
 
-      bool setTaboo( ResStatus::TransactByValue by_r ) const
+      bool setTaboo()
       {
-         if ( cand ) {
-             cand.status().setTransact( false, by_r );
-             return cand.status().setLock( true, by_r );
-         } else
-             return false;
+        if ( causer != ResStatus::USER ) // by user only
+          return false;
+
+        if ( cand ) {
+          lockCandidates();
+          return true;
+        } else
+          return false;
       }
+      //@}
 
+    private:
+      /** \name Helper methods backup status but do not replay. */
+      //@{
+      bool resetTransactingCandidates()
+      {
+        for_( it, _impl.availableBegin(), _impl.availableEnd() )
+        {
+          ResStatus & status( backup( (*it).status() ) );
+          if ( ! status.setTransact( false, causer ) ) return false;
+        }
+        return true;
+      }
+      bool unlockCandidates()
+      {
+        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 lockCandidates()
+      {
+        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;
     };
     ///////////////////////////////////////////////////////////////////
 
@@ -134,7 +322,6 @@ namespace zypp
     Status Selectable::Impl::status() const
     {
       PoolItem cand( candidateObj() );
-
       if ( cand && cand.status().transacts() )
         {
           if ( cand.status().isByUser() )
@@ -148,25 +335,33 @@ namespace zypp
           return( installedObj().status().isByUser() ? S_Del : S_AutoDel );
         }
 
-      if ( installedObj() && installedObj().status().isLocked() )
+      if ( installedObj() && allInstalledLocked() )
          return S_Protected;
 
-      if ( !installedObj() && cand && cand.status().isLocked() )
+      if ( !installedObj() && allCandidatesLocked() )
          return S_Taboo;
 
-      return( installedObj() ? S_KeepInstalled : S_NoInst );
+      // KEEP state:
+      if ( installedObj() )
+        return S_KeepInstalled;
+      // Report pseudo installed items as installed, if they are satisfied.
+      if ( traits::isPseudoInstalled( kind() )
+           && cand.status().isSatisfied() ) // no installed, so we must have candidate
+        return S_KeepInstalled;
+
+      return S_NoInst;
     }
 
-    bool Selectable::Impl::set_status( const Status state_r )
+    bool Selectable::Impl::setStatus( Status state_r, ResStatus::TransactByValue causer_r )
     {
-      StatusHelper self( *this );
+      StatusHelper self( *this, causer_r );
 
       switch ( state_r )
         {
         case S_Protected:
-           return self.setProtected( ResStatus::USER );
+           return self.setProtected();
         case S_Taboo:
-           return self.setTaboo( ResStatus::USER );
+           return self.setTaboo();
         case S_AutoDel:
         case S_AutoInstall:
         case S_AutoUpdate:
@@ -175,53 +370,278 @@ namespace zypp
           break;
 
         case S_Del:
-          return self.setDelete( ResStatus::USER );
+          return self.setDelete();
           break;
 
         case S_Install:
-          return self.hasCandidateOnly() && self.setInstall( ResStatus::USER );
+          return self.hasCandidateOnly() && self.setInstall();
           break;
 
         case S_Update:
-          return self.hasBoth() && self.setInstall( ResStatus::USER );
+          return self.hasBoth() && self.setInstall();
           break;
 
         case S_KeepInstalled:
-          return self.hasInstalled() && self.unset( ResStatus::USER );
+          return self.hasInstalled() && self.unset();
           break;
 
         case S_NoInst:
-          return !self.hasInstalled() && self.unset( ResStatus::USER );
+          return !self.hasInstalled() && self.unset();
           break;
         }
 
       return false;
     }
 
-    PoolItem Selectable::Impl::setCandidate( ResObject::constPtr byUser_r )
+    PoolItem Selectable::Impl::setCandidate( const PoolItem & newCandidate_r, ResStatus::TransactByValue causer_r )
     {
-      _candidate = PoolItem();
-      for ( availableItem_const_iterator it = availableBegin();
-            it != availableEnd(); ++it )
+      PoolItem newCandidate;
+
+      if ( newCandidate_r ) // must be in available list
+      {
+        for_( it, availableBegin(), availableEnd() )
         {
-          if ( it->resolvable() == byUser_r )
-            {
-              _candidate = *it;
-              break;
-            }
+          if ( *it == newCandidate_r )
+          {
+            newCandidate = *it;
+            break;
+          }
+        }
+      }
+
+      if ( newCandidate )
+      {
+        PoolItem trans( transactingCandidate() );
+        if ( trans && trans != newCandidate )
+        {
+          // 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.ident() != ident() )
+        return false;  // not my PoolItem
+
+      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() )
+         {
+           const PoolItem & cand( pi_r.status().isInstalled() ? *a.begin() : pi_r );
+           if ( cand.multiversionInstall() )
+           {
+             if ( ! backup.forEach( availableBegin(), availableEnd(), &StatusBackup::unsetNonMultiTransact, causer_r ) ) return backup.restore();
+             // maybe unlock candidate only?
+             if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+             if ( ! cand.status().setTransact( true, causer_r ) ) return backup.restore();
+             return true;
+           }
+           else
+           {
+             // For non-multiversion use ordinary setStatus
+             // NOTE that S_Update/S_Install here depends on !installedEmpty()
+             // and not on picklists identicalInstalled.
+             if ( ! backup.forEach( availableBegin(), availableEnd(), &StatusBackup::unsetMultiTransact, causer_r ) ) return backup.restore();
+             if ( ! setCandidate( cand, causer_r ) )  return backup.restore();
+             if ( ! setStatus( installedEmpty() ? S_Install : S_Update, causer_r ) )  return backup.restore();
+             return true;
+           }
+         }
+         break;
+
+       case S_Update:
+         if ( ! i.empty() && ! a.empty() )
+         {
+           const PoolItem & cand( pi_r.status().isInstalled() ? *a.begin() : pi_r );
+           if ( cand.multiversionInstall() )
+           {
+             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();
+             if ( ! backup.forEach( availableBegin(), availableEnd(), &StatusBackup::unsetNonMultiTransact, causer_r ) ) return backup.restore();
+             // maybe unlock candidate only?
+             if ( ! backup.forEach( a.begin(), a.end(), &StatusBackup::unlock, causer_r ) ) return backup.restore();
+             if ( ! cand.status().setTransact( true, causer_r ) ) return backup.restore();
+             return true;
+           }
+           else
+           {
+             // For non-multiversion use ordinary setStatus
+             // NOTE that S_Update/S_Install here depends on !installedEmpty()
+             // and not on picklists identicalInstalled.
+             if ( ! backup.forEach( availableBegin(), availableEnd(), &StatusBackup::unsetMultiTransact, causer_r ) ) return backup.restore();
+             if ( ! setCandidate( cand, causer_r ) )  return backup.restore();
+             if ( ! setStatus( installedEmpty() ? S_Install : S_Update, 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;
         }
-      if ( ! ( _candidate || _availableItems.empty() ) )
+
+      for_( it, availableBegin(), availableEnd() )
+        if ( identical( *it, pi_r ) )
         {
-#warning actually select with respect to an installed items arch
-          _candidate = *_availableItems.begin();
+          a.push_back( *it );
+          if ( ! ta && it->status().transacts() )
+            ta = *it;
         }
-      return _candidate;
+
+      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() );
-
       if ( cand && cand.status().transacts() )
         return cand.status().getTransactByValue();