Logo Search packages:      
Sourcecode: partitionmanager version File versions  Download package

operationstack.cpp

/***************************************************************************
 *   Copyright (C) 2008 by Volker Lanz <vl@fidra.de>                       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
 ***************************************************************************/

#include "core/operationstack.h"
#include "core/device.h"
#include "core/partition.h"
#include "core/partitiontable.h"

#include "ops/operation.h"
#include "ops/deleteoperation.h"
#include "ops/newoperation.h"
#include "ops/resizeoperation.h"
#include "ops/copyoperation.h"
#include "ops/restoreoperation.h"
#include "ops/createfilesystemoperation.h"
#include "ops/setpartflagsoperation.h"
#include "ops/setfilesystemlabeloperation.h"

#include "jobs/setfilesystemlabeljob.h"

#include "fs/filesystemfactory.h"

#include "util/globallog.h"


#include <kdebug.h>
#include <klocale.h>

/** Constructs a new OperationStack */
00046 OperationStack::OperationStack() :
      m_Operations(),
      m_PreviewDevices()
{
}

/** Destructs an OperationStack, cleaning up Operations and Devices */
00053 OperationStack::~OperationStack()
{
      clearOperations();
      clearDevices();
}

/** Tries to merge an existing NewOperation with a new Operation pushed on the OperationStack

      There are several cases what might need to be done:

      <ol>
      <!-- 1 -->
      <li>An existing operation created a Partition that is now being deleted: In this case, just remove
            the corresponding NewOperation from the OperationStack.</li>
      <!-- 2 -->
      <li>An existing Operation created a Partition that is now being moved or resized. In this case,
            remove the original NewOperation and create a new NewOperation with updated start and end
            sectors. This new NewOperation is appended to the OperationStack.</li>
      <!-- 3 -->
      <li>An existing NewOperation created a Partition that is now being copied. We're not copying
            but instead creating another new Partition in its place.</li>
      <!-- 4 -->
      <li>The label for a new Partition's FileSystem is modified: Modify in NewOperation and forget it.</li>
      <!-- 5 -->
      <li>File system is changed for a new Partition: Modify in NewOperation and forget it.</li>
      </ol>

      @param currentOp the Operation already on the stack to try to merge with
      @param pushedOp the newly pushed Operation
      @return true if the OperationStack has been modified in a way that requires merging to stop
*/
00084 bool OperationStack::mergeNewOperation(Operation*& currentOp, Operation*& pushedOp)
{
      NewOperation* newOp = dynamic_cast<NewOperation*>(currentOp);

      if (newOp == NULL)
            return false;

      DeleteOperation* pushedDeleteOp = dynamic_cast<DeleteOperation*>(pushedOp);
      ResizeOperation* pushedResizeOp = dynamic_cast<ResizeOperation*>(pushedOp);
      CopyOperation* pushedCopyOp = dynamic_cast<CopyOperation*>(pushedOp);
      SetFileSystemLabelOperation* pushedLabelOp = dynamic_cast<SetFileSystemLabelOperation*>(pushedOp);
      CreateFileSystemOperation* pushedCreateFileSystemOp = dynamic_cast<CreateFileSystemOperation*>(pushedOp);

      // -- 1 --
      if (pushedDeleteOp && &newOp->newPartition() == &pushedDeleteOp->deletedPartition())
      {
            log() << i18nc("@info/plain", "Deleting a partition just created: Undoing the operation to create the partition.");

            delete pushedOp;
            pushedOp = NULL;

            newOp->undo();
            delete operations().takeAt(operations().indexOf(newOp));

            return true;
      }

      // -- 2 --
      if (pushedResizeOp && &newOp->newPartition() == &pushedResizeOp->partition())
      {
            log() << i18nc("@info/plain", "Resizing a partition just created: Updating start and end in existing operation.");

            Partition* newPartition = new Partition(newOp->newPartition());
            newPartition->setFirstSector(pushedResizeOp->newFirstSector());
            newPartition->fileSystem().setFirstSector(pushedResizeOp->newFirstSector());
            newPartition->setLastSector(pushedResizeOp->newLastSector());
            newPartition->fileSystem().setLastSector(pushedResizeOp->newLastSector());

            NewOperation* revisedNewOp = new NewOperation(newOp->targetDevice(), newPartition);
            delete pushedOp;
            pushedOp = revisedNewOp;

            newOp->undo();
            delete operations().takeAt(operations().indexOf(newOp));

            return true;
      }

      // -- 3 --
      if (pushedCopyOp && &newOp->newPartition() == &pushedCopyOp->sourcePartition())
      {
            log() << i18nc("@info/plain", "Copying a new partition: Creating a new partition instead.");

            Partition* newPartition = new Partition(newOp->newPartition());
            newPartition->setFirstSector(pushedCopyOp->copiedPartition().firstSector());
            newPartition->fileSystem().setFirstSector(pushedCopyOp->copiedPartition().fileSystem().firstSector());
            newPartition->setLastSector(pushedCopyOp->copiedPartition().lastSector());
            newPartition->fileSystem().setLastSector(pushedCopyOp->copiedPartition().fileSystem().lastSector());

            NewOperation* revisedNewOp = new NewOperation(pushedCopyOp->targetDevice(), newPartition);
            delete pushedOp;
            pushedOp = revisedNewOp;

            return true;
      }

      // -- 4 --
      if (pushedLabelOp && &newOp->newPartition() == &pushedLabelOp->labeledPartition())
      {
            log() << i18nc("@info/plain", "Changing label for a new partition: No new operation required.");

            newOp->setLabelJob()->setLabel(pushedLabelOp->newLabel());
            newOp->newPartition().fileSystem().setLabel(pushedLabelOp->newLabel());

            delete pushedOp;
            pushedOp = NULL;

            return true;
      }

      // -- 5 --
      if (pushedCreateFileSystemOp && &newOp->newPartition() == &pushedCreateFileSystemOp->partition())
      {
            log() << i18nc("@info/plain", "Changing file system for a new partition: No new operation required.");

            FileSystem* oldFs = &newOp->newPartition().fileSystem();

            newOp->newPartition().setFileSystem(FileSystemFactory::cloneWithNewType(pushedCreateFileSystemOp->newFileSystem()->type(), *oldFs));

            delete oldFs;
            oldFs = NULL;

            delete pushedOp;
            pushedOp = NULL;

            return true;
      }

      return false;
}

/**   Tries to merge an existing CopyOperation with a new Operation pushed on the OperationStack.

      These are the cases to consider:

      <ol>
      <!-- 1 -->
      <li>An existing CopyOperation created a Partition that is now being deleted. Remove the
            CopyOperation, and, if the CopyOperation was an overwrite, carry on with the delete. Else
            also remove the DeleteOperation.</li>

      <!-- 2 -->
      <li>An existing CopyOperation created a Partition that is now being copied. We're not copying
            the target of this existing CopyOperation, but its source instead. If this merge is not done,
            copied partitions will have misleading labels ("copy of sdXY" instead of "copy of copy of
            sdXY" for a second-generation copy) but the Operation itself will still work.</li>
      </ol>

      @param currentOp the Operation already on the stack to try to merge with
      @param pushedOp the newly pushed Operation
      @return true if the OperationStack has been modified in a way that requires merging to stop
*/
00206 bool OperationStack::mergeCopyOperation(Operation*& currentOp, Operation*& pushedOp)
{
      CopyOperation* copyOp = dynamic_cast<CopyOperation*>(currentOp);

      if (copyOp == NULL)
            return false;

      DeleteOperation* pushedDeleteOp = dynamic_cast<DeleteOperation*>(pushedOp);
      CopyOperation* pushedCopyOp = dynamic_cast<CopyOperation*>(pushedOp);

      // -- 1 --
      if (pushedDeleteOp && &copyOp->copiedPartition() == &pushedDeleteOp->deletedPartition())
      {
            // If the copy operation didn't overwrite, but create a new partition, just remove the
            // copy operation, forget the delete and be done.
            if (copyOp->overwrittenPartition() == NULL)
            {
                  log() << i18nc("@info/plain", "Deleting a partition just copied: Removing the copy.");

                  delete pushedOp;
                  pushedOp = NULL;
            }
            else
            {
                  log() << i18nc("@info/plain", "Deleting a partition just copied over an existing partition: Removing the copy and deleting the existing partition.");

                  pushedDeleteOp->setDeletedPartition(copyOp->overwrittenPartition());
            }

            copyOp->undo();
            delete operations().takeAt(operations().indexOf(copyOp));

            return true;
      }

      // -- 2 --
      if (pushedCopyOp && &copyOp->copiedPartition() == &pushedCopyOp->sourcePartition())
      {
            log() << i18nc("@info/plain", "Copying a partition that is itself a copy: Copying the original source partition instead.");

            pushedCopyOp->setSourcePartition(&copyOp->sourcePartition());
      }

      return false;
}

/** Tries to merge an existing RestoreOperation with a new Operation pushed on the OperationStack.

      If an existing RestoreOperation created a Partition that is now being deleted, remove the
      RestoreOperation, and, if the RestoreOperation was an overwrite, carry on with the delete. Else
      also remove the DeleteOperation.

      @param currentOp the Operation already on the stack to try to merge with
      @param pushedOp the newly pushed Operation
      @return true if the OperationStack has been modified in a way that requires merging to stop
*/
00262 bool OperationStack::mergeRestoreOperation(Operation*& currentOp, Operation*& pushedOp)
{
      RestoreOperation* restoreOp = dynamic_cast<RestoreOperation*>(currentOp);

      if (restoreOp == NULL)
            return false;

      DeleteOperation* pushedDeleteOp = dynamic_cast<DeleteOperation*>(pushedOp);

      if (pushedDeleteOp && &restoreOp->restorePartition() == &pushedDeleteOp->deletedPartition())
      {
            if (restoreOp->overwrittenPartition() == NULL)
            {
                  log() << i18nc("@info/plain", "Deleting a partition just restored: Removing the restore operation.");

                  delete pushedOp;
                  pushedOp = NULL;
            }
            else
            {
                  log() << i18nc("@info/plain", "Deleting a partition just restored to an existing partition: Removing the restore operation and deleting the existing partition.");

                  pushedDeleteOp->setDeletedPartition(restoreOp->overwrittenPartition());
            }

            restoreOp->undo();
            delete operations().takeAt(operations().indexOf(restoreOp));

            return true;
      }

      return false;
}

/** Tries to merge an existing SetPartFlagsOperation with a new Operation pushed on the OperationStack.

      If the Partition flags for an existing Partition are modified look if there is an existing
      Operation for the same Partition and modify that one.

      @param currentOp the Operation already on the stack to try to merge with
      @param pushedOp the newly pushed Operation
      @return true if the OperationStack has been modified in a way that requires merging to stop
*/
00305 bool OperationStack::mergePartFlagsOperation(Operation*& currentOp, Operation*& pushedOp)
{
      SetPartFlagsOperation* partFlagsOp = dynamic_cast<SetPartFlagsOperation*>(currentOp);

      if (partFlagsOp == NULL)
            return false;

      SetPartFlagsOperation* pushedFlagsOp = dynamic_cast<SetPartFlagsOperation*>(pushedOp);

      if (pushedFlagsOp && &partFlagsOp->flagPartition() == &pushedFlagsOp->flagPartition())
      {
            log() << i18nc("@info/plain", "Changing flags again for the same partition: Removing old operation.");

            pushedFlagsOp->setOldFlags(partFlagsOp->oldFlags());
            partFlagsOp->undo();
            delete operations().takeAt(operations().indexOf(partFlagsOp));

            return true;
      }

      return false;
}

/** Tries to merge an existing SetFileSystemLabelOperation with a new Operation pushed on the OperationStack.

      If a FileSystem label for an existing Partition is modified look if there is an existing
      SetFileSystemLabelOperation for the same Partition.

      @param currentOp the Operation already on the stack to try to merge with
      @param pushedOp the newly pushed Operation
      @return true if the OperationStack has been modified in a way that requires merging to stop
*/
00337 bool OperationStack::mergePartLabelOperation(Operation*& currentOp, Operation*& pushedOp)
{
      SetFileSystemLabelOperation* partLabelOp = dynamic_cast<SetFileSystemLabelOperation*>(currentOp);

      if (partLabelOp == NULL)
            return false;

      SetFileSystemLabelOperation* pushedLabelOp = dynamic_cast<SetFileSystemLabelOperation*>(pushedOp);

      if (pushedLabelOp && &partLabelOp->labeledPartition() == &pushedLabelOp->labeledPartition())
      {
            log() << i18nc("@info/plain", "Changing label again for the same partition: Removing old operation.");

            pushedLabelOp->setOldLabel(partLabelOp->oldLabel());
            partLabelOp->undo();
            delete operations().takeAt(operations().indexOf(partLabelOp));

            return true;
      }

      return false;
}

/** Pushes a new Operation on the OperationStack.

      This method will call all methods that try to merge the new Operation with the
      existing ones. It is not uncommon that any of these will delete the pushed
      Operation. Callers <b>must not rely</b> on the pushed Operation to exist after
      calling OperationStack::push().

      @param o Pointer to the Operation. Must not be NULL.
*/
00369 void OperationStack::push(Operation* o)
{
      Q_ASSERT(o);

      foreach (Operation* currentOp, operations())
      {
            if (mergeNewOperation(currentOp, o))
                  break;

            if (mergeCopyOperation(currentOp, o))
                  break;

            if (mergeRestoreOperation(currentOp, o))
                  break;

            if (mergePartFlagsOperation(currentOp, o))
                  break;

            if (mergePartLabelOperation(currentOp, o))
                  break;
      }

      if (o != NULL)
      {
            log() << i18nc("@info/plain", "Add operation: %1", o->description());
            operations().append(o);
            o->preview();
            o->setStatus(Operation::StatusPending);
      }
}

/** Removes the topmost Operation from the OperationStack, calls Operation::undo() on it and deletes it. */
00401 void OperationStack::pop()
{
      Operation* o = operations().takeLast();
      o->undo();
      delete o;
}

/** Removes all Operations from the OperationStack, calling Operation::undo() on them and deleting them. */
00409 void OperationStack::clearOperations()
{
      while (!operations().isEmpty())
      {
            Operation* o = operations().takeLast();
            if (o->status() == Operation::StatusPending)
                  o->undo();
            delete o;
      }
}

/** Clears the list of Devices. */
00421 void OperationStack::clearDevices()
{
      qDeleteAll(m_PreviewDevices);
      m_PreviewDevices.clear();
}

/** Finds a Device a Partition is on.
      @param p pointer to the Partition to find a Device for
      @return the Device or NULL if none could be found
*/
00431 Device* OperationStack::findDeviceForPartition(const Partition* p)
{
      foreach (Device* d, previewDevices())
      {
            if (d->partitionTable() == NULL)
                  continue;

            foreach(const Partition* part, d->partitionTable()->children())
            {
                  if (part == p)
                        return d;

                  foreach (const Partition* child, part->children())
                        if (child == p)
                              return d;
            }
      }

      return NULL;
}

/** Adds a Device to the OperationStack
      @param d pointer to the Device to add. Must not be NULL.
*/
00455 void OperationStack::addDevice(Device* d)
{
      Q_ASSERT(d);

      m_PreviewDevices.append(d);
}

Generated by  Doxygen 1.6.0   Back to index