Skip to content

Commit

Permalink
Added folder service tests
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidLazarescu committed Feb 11, 2024
1 parent 954842a commit 36b59e5
Show file tree
Hide file tree
Showing 6 changed files with 563 additions and 129 deletions.
258 changes: 131 additions & 127 deletions src/application/services/folder_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,42 @@ Folder* FolderService::getFolder(const QUuid& uuid)
return getFolderHelper(uuid, m_rootFolder.get());
}

Folder* FolderService::getFolderHelper(const QUuid& uuid, Folder* curr)
{
if(curr->getUuid() == uuid)
return curr;

for(auto& child : curr->getChildren())
{
auto result = getFolderHelper(uuid, child.get());
if(result != nullptr)
return result;
}

return nullptr;
}

bool FolderService::createFolder(const QString& name, QString color,
QString icon, QString description,
const QUuid& parent)
{
Folder* parentFolder = nullptr;
// This means that the folder should be created in the root folder.
if(parent.isNull())
if(parent.isNull()) // Set the root folder as the parent
parentFolder = m_rootFolder.get();
else
parentFolder = getFolder(parent);

if(parentFolder == nullptr)
{
qWarning() << "Creating folder failed. Could not find parent folder "
"with uuid: "
<< parent;
return false;
}

// Set the index in parent to the current child count, since we're adding
// the folder to the end of the parent's children.
auto child = std::make_unique<Folder>(name, color, icon, description);
child->setIndexInParent(parentFolder->childCount());

emit beginInsertFolder(parentFolder, parentFolder->childCount());
parentFolder->addChild(std::move(child));
emit endInsertFolder();
auto newFolder = std::make_unique<Folder>(name, color, icon, description);
createFolderHelper(std::move(newFolder), parentFolder);

parentFolder->updateLastModified();
saveChanges();
return true;
}
Expand All @@ -71,29 +83,35 @@ QList<QString> FolderService::deleteFolder(const QUuid& uuid)
{
auto folder = getFolder(uuid);
if(folder == nullptr)
{
qWarning()
<< "Deleting folder failed. Could not find folder with uuid: "
<< uuid;
return {};
}

auto parent = folder->getParent();
auto indexInParent = folder->getIndexInParent();

// We return a list of all the folders that are deleted (since all
// descendents of the folder are also deleted).
// We return a list of all the folders that are deleted (all
// descendents of the folder are deleted as well).
auto listOfDescendents = getUuidsOfAllDescendents(*folder);
listOfDescendents.push_front(uuid.toString(QUuid::WithoutBraces));

emit beginRemoveFolder(parent, indexInParent);
parent->removeChild(uuid);
emit endRemoveFolder();
deleteFolderHelper(folder);

parent->decreaseChildIndiciesIfBiggerThan(indexInParent);
parent->updateLastModified();
saveChanges();
return listOfDescendents;
}

void FolderService::updateFolder(const domain::entities::Folder& folder)
{
auto realFolder = getFolder(folder.getUuid());
if(realFolder == nullptr)
{
qWarning()
<< "Updating folder failed. Could not find folder with uuid: "
<< folder.getUuid();
return;
}

realFolder->setName(folder.getName());
realFolder->setColor(folder.getColor());
realFolder->setIcon(folder.getIcon());
Expand All @@ -108,6 +126,12 @@ bool FolderService::moveFolder(const QUuid& uuid, const QUuid& destUuid)
{
auto currFolder = getFolder(uuid);
auto destFolder = getFolder(destUuid);
if(currFolder == nullptr || destFolder == nullptr)
{
qWarning() << "Folder move operation failed. Could not find source or "
"destination";
return false;
}

// We don't need to do anything if dest already is the parent.
if(currFolder->getParent()->getUuid() == destFolder->getUuid())
Expand All @@ -121,27 +145,12 @@ bool FolderService::moveFolder(const QUuid& uuid, const QUuid& destUuid)
}

// Make copy since we delete the currFolder from its parent which frees
// it's memory.
// the original folder's memory.
auto currFolderCopy = std::make_unique<Folder>(*currFolder);

auto currentParent = currFolder->getParent();
// Since we remove the child from it's old parent, all other children's
// indecies need to be adjusted.
currentParent->decreaseChildIndiciesIfBiggerThan(
currFolderCopy->getIndexInParent());
deleteFolderHelper(currFolder);
createFolderHelper(std::move(currFolderCopy), destFolder);

emit beginRemoveFolder(currFolder->getParent(),
currFolder->getIndexInParent());
currentParent->removeChild(uuid);
currentParent->updateLastModified();
emit endRemoveFolder();

currFolderCopy->setIndexInParent(destFolder->childCount());
emit beginInsertFolder(destFolder, destFolder->childCount());
destFolder->addChild(std::move(currFolderCopy));
emit endInsertFolder();

destFolder->updateLastModified();
saveChanges();
return true;
}
Expand All @@ -150,59 +159,54 @@ void FolderService::setupUserData(const QString& token, const QString& email)
{
m_authenticationToken = token;
m_localLibraryTracker->setLibraryOwner(email);
m_rootFolder->getChildren().clear();

auto folder = m_localLibraryTracker->loadFolders();

// This occurs when the user has no library yet. If this occurs, we want to
// create a default folder under the root folder for them.
if(folder.getName() == "invalid")
{
auto defaultFolder = std::make_unique<Folder>(
"Archive", "default", "folder", "A folder for archived books");
defaultFolder->setIndexInParent(0);

m_rootFolder->addChild(std::move(defaultFolder));
}
if(folder.getName() == "invalid") // No local library exists yet
createDefaultFolder();
else
{
// Update the root folder's uuid with the stored uuid
m_rootFolder->setUuid(folder.getUuid());
m_rootFolder->setLastModified(folder.getLastModified());

// We need to add the folder's children to the current root element,
// since a pointer of the root element was already passed to the model,
// so we can't simply overwrite it, else we'd invalidate the pointer.
for(auto& child : folder.getChildren())
m_rootFolder->addChild(std::move(child));

// JSON format does not guarantee that the children are sorted.
m_rootFolder->sortDescendents();
}
overwriteRootFolderWith(folder);

// Trigger a timeout manually at the start for the initial folder loading
m_fetchChangesTimer.start();
QMetaObject::invokeMethod(&m_fetchChangesTimer, "timeout");
}

void FolderService::clearUserData()
void FolderService::createDefaultFolder()
{
m_localLibraryTracker->clearLibraryOwner();
auto defaultFolder = std::make_unique<Folder>(
"Archive", "default", "folder", "A folder for archived books");
defaultFolder->setIndexInParent(0);

m_rootFolder->addChild(std::move(defaultFolder));
}

Folder* FolderService::getFolderHelper(const QUuid& uuid,
domain::entities::Folder* parent)
void FolderService::overwriteRootFolderWith(Folder& folder)
{
if(parent->getUuid() == uuid)
return parent;
m_rootFolder->setUuid(folder.getUuid());
m_rootFolder->setLastModified(folder.getLastModified());
m_rootFolder->getChildren().clear();

for(auto& child : parent->getChildren())
{
auto result = getFolderHelper(uuid, child.get());
if(result != nullptr)
return result;
}
// We need to add the folder's children to the current root element,
// since a pointer of the root element was already passed to the folders
// model, so we can't simply overwrite it, else we'd invalidate the pointer.
for(auto& child : folder.getChildren())
m_rootFolder->addChild(std::move(child));

return nullptr;
// JSON format does not guarantee that the children are in the correct order
// when loaded, so we need to sort them.
m_rootFolder->sortDescendents();
}

void FolderService::clearUserData()
{
m_localLibraryTracker->clearLibraryOwner();

m_rootFolder->getChildren().clear();
m_rootFolder->setUuid(QUuid());
m_rootFolder->setLastModified(QDateTime::currentDateTime());

m_fetchChangesTimer.stop();
}

void FolderService::saveChanges()
Expand All @@ -213,103 +217,75 @@ void FolderService::saveChanges()

void FolderService::processFetchedFolders(Folder& remoteRoot)
{
// This occurs when there is no folder on the server yet. We want to create
// a default folder for the user.
if(remoteRoot.getName().isEmpty())
// This occurs when there is no folder on the server yet. If that's the
// case, we just want to sync whatever we have locally to the server.
if(remoteRoot.getName() == "")
{
m_folderStorageGateway->updateFolder(m_authenticationToken,
*m_rootFolder);
return;
}

// This occurs when there is folder data on the server, but there is no
// local folder data on the client yet. E.g. when starting the application
// for the first time from a new machine, while folders already exist on the
// server.
// In this case we want to just take all of the data from the server by
// setting the rootFolder's uuid, removing the child created by default and
// assigning the server folder's children to the root folder.
// This occurs when there is folder data on the server, but it wasn never
// synced with the client (thus the root uuids are different). In this case,
// we just want to overwrite the local folders with the remote folders.
auto localRoot = getFolder(remoteRoot.getUuid());
if(localRoot == nullptr)
{
m_rootFolder->setUuid(remoteRoot.getUuid());
m_rootFolder->removeChild(m_rootFolder->getChildAtIndex(0)->getUuid());

emit beginModelReset();
for(auto& child : remoteRoot.getChildren())
m_rootFolder->addChild(std::move(child));

m_rootFolder->sortDescendents();
overwriteRootFolderWith(remoteRoot);
emit endModelReset();
}
// We don't need update the local folders if we copy over everything from
// the server since local and server will be exactly the same.
else
{
emit beginModelReset();
updateFoldersRecursively(localRoot, remoteRoot);
m_rootFolder->sortDescendents();
emit endModelReset();
}

saveChanges();
}

void FolderService::updateFoldersRecursively(Folder* current,
Folder& remoteRoot)
void FolderService::updateFoldersRecursively(Folder* curr, Folder& remoteRoot)
{
Folder* remoteFolder = nullptr;
if(current == m_rootFolder.get())
if(curr == m_rootFolder.get())
remoteFolder = &remoteRoot;
else
remoteFolder = remoteRoot.getDescendant(current->getUuid());
remoteFolder = remoteRoot.getDescendant(curr->getUuid());

// This occurs when the remote folder was added, and thus moved from, during
// the 'addMissingChildrenToFolder' step. We just skip it.
// This occurs when the remote folder was added locally, and thus moved
// from. We just skip it since it was already dealt with.
if(remoteFolder == nullptr)
return;

auto currLastModified = current->getLastModified().toSecsSinceEpoch();
auto currLastModified = curr->getLastModified().toSecsSinceEpoch();
auto remoteLastModified =
remoteFolder->getLastModified().toSecsSinceEpoch();
if(remoteLastModified > currLastModified)
{
current->updateProperties(*remoteFolder);
curr->updateProperties(*remoteFolder);

// We don't want to emit refreshFolder for the root folder
if(current->getName() != "ROOT")
{
emit refreshFolder(current->getParent(),
current->getIndexInParent());
}
if(curr->getName() != "ROOT")
emit refreshFolder(curr->getParent(), curr->getIndexInParent());

// If the remote folder has changed, we treat it as the source of
// truth and replace the whole subtree of the current folder with the
// remote subtree.
QList<QUuid> foldersToRemove;
for(auto& child : current->getChildren())
foldersToRemove.push_back(child->getUuid());

for(auto& uuid : foldersToRemove)
{
emit beginRemoveFolder(current, current->getIndexOfChild(uuid));
current->removeChild(uuid);
emit endRemoveFolder();
}

curr->getChildren().clear();
for(auto& child : remoteFolder->getChildren())
{
child->setParent(current);
emit beginInsertFolder(current, current->childCount());
current->addChild(std::move(child));
emit endInsertFolder();
child->setIndexInParent(curr->childCount());
curr->addChild(std::make_unique<Folder>(*child));
}

return;
}

for(auto& child : current->getChildren())
{
for(auto& child : curr->getChildren())
updateFoldersRecursively(child.get(), remoteRoot);
}
}

QList<QString> FolderService::getUuidsOfAllDescendents(const Folder& folder)
Expand All @@ -325,4 +301,32 @@ QList<QString> FolderService::getUuidsOfAllDescendents(const Folder& folder)
return uuids;
}

void FolderService::createFolderHelper(std::unique_ptr<Folder> folder,
Folder* parent)
{
folder->setIndexInParent(parent->childCount());

emit beginInsertFolder(parent, parent->childCount());
parent->addChild(std::move(folder));
emit endInsertFolder();

parent->updateLastModified();
}

void FolderService::deleteFolderHelper(Folder* folder)
{
int indexInParent = folder->getIndexInParent();
auto parent = folder->getParent();

emit beginRemoveFolder(folder->getParent(), indexInParent);
auto success = parent->removeChild(folder->getUuid());
emit endRemoveFolder();

if(success)
{
parent->decreaseChildIndiciesIfBiggerThan(indexInParent);
parent->updateLastModified();
}
}

} // namespace application::services
Loading

0 comments on commit 36b59e5

Please sign in to comment.