/*************************************************************************** * Copyright (C) 2006 by Sébastien Laoût * * slaout@linux62.org * * * * 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. * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "archive.h" #include "global.h" #include "bnpview.h" #include "basket.h" #include "basketlistview.h" #include "basketfactory.h" #include "tag.h" #include "xmlwork.h" #include "tools.h" #include "backgroundmanager.h" #include "formatimporter.h" #include void Archive::save(Basket *basket, bool withSubBaskets, const TQString &destination) { TQDir dir; KProgressDialog dialog(0, 0, i18n("Save as Basket Archive"), i18n("Saving as basket archive. Please wait..."), /*Not modal, for password dialogs!*/false); dialog.showCancelButton(false); dialog.setAutoClose(true); dialog.show(); KProgress *progress = dialog.progressBar(); progress->setTotalSteps(/*Preparation:*/1 + /*Finishing:*/1 + /*Basket:*/1 + /*SubBaskets:*/(withSubBaskets ? Global::bnpView->basketCount(Global::bnpView->listViewItemForBasket(basket)) : 0)); progress->setValue(0); // Create the temporar folder: TQString tempFolder = Global::savesFolder() + "temp-archive/"; dir.mkdir(tempFolder); // Create the temporar archive file: TQString tempDestination = tempFolder + "temp-archive.tar.gz"; KTar tar(tempDestination, "application/x-gzip"); tar.open(IO_WriteOnly); tar.writeDir("baskets", "", ""); progress->advance(1); // Preparation finished std::cout << "Preparation finished out of " << progress->totalSteps() << std::endl; // Copy the baskets data into the archive: TQStringList backgrounds; saveBasketToArchive(basket, withSubBaskets, &tar, backgrounds, tempFolder, progress); // Create a Small baskets.xml Document: TQDomDocument document("basketTree"); TQDomElement root = document.createElement("basketTree"); document.appendChild(root); Global::bnpView->saveSubHierarchy(Global::bnpView->listViewItemForBasket(basket), document, root, withSubBaskets); Basket::safelySaveToFile(tempFolder + "baskets.xml", "\n" + document.toString()); tar.addLocalFile(tempFolder + "baskets.xml", "baskets/baskets.xml"); dir.remove(tempFolder + "baskets.xml"); // Save a Small tags.xml Document: TQValueList tags; listUsedTags(basket, withSubBaskets, tags); Tag::saveTagsTo(tags, tempFolder + "tags.xml"); tar.addLocalFile(tempFolder + "tags.xml", "tags.xml"); dir.remove(tempFolder + "tags.xml"); // Save Tag Emblems (in case they are loaded on a computer that do not have those icons): TQString tempIconFile = tempFolder + "icon.png"; for (Tag::List::iterator it = tags.begin(); it != tags.end(); ++it) { State::List states = (*it)->states(); for (State::List::iterator it2 = states.begin(); it2 != states.end(); ++it2) { State *state = (*it2); TQPixmap icon = kapp->iconLoader()->loadIcon(state->emblem(), TDEIcon::Small, 16, TDEIcon::DefaultState, /*path_store=*/0L, /*canReturnNull=*/true); if (!icon.isNull()) { icon.save(tempIconFile, "PNG"); TQString iconFileName = state->emblem().replace('/', '_'); tar.addLocalFile(tempIconFile, "tag-emblems/" + iconFileName); } } } dir.remove(tempIconFile); // Finish Tar.Gz Exportation: tar.close(); // Computing the File Preview: Basket *previewBasket = basket; // FIXME: Use the first non-empty basket! TQPixmap previewPixmap(previewBasket->visibleWidth(), previewBasket->visibleHeight()); TQPainter painter(&previewPixmap); // Save old state, and make the look clean ("smile, you are filmed!"): NoteSelection *selection = previewBasket->selectedNotes(); previewBasket->unselectAll(); Note *focusedNote = previewBasket->focusedNote(); previewBasket->setFocusedNote(0); previewBasket->doHoverEffects(0, Note::None); // Take the screenshot: previewBasket->drawContents(&painter, 0, 0, previewPixmap.width(), previewPixmap.height()); // Go back to the old look: previewBasket->selectSelection(selection); previewBasket->setFocusedNote(focusedNote); previewBasket->doHoverEffects(); // End and save our splandid painting: painter.end(); TQImage previewImage = previewPixmap.convertToImage(); const int PREVIEW_SIZE = 256; previewImage = previewImage.scale(PREVIEW_SIZE, PREVIEW_SIZE, TQ_ScaleMin); previewImage.save(tempFolder + "preview.png", "PNG"); // Finaly Save to the Real Destination file: TQFile file(destination); if (file.open(IO_WriteOnly)) { ulong previewSize = TQFile(tempFolder + "preview.png").size(); ulong archiveSize = TQFile(tempDestination).size(); TQTextStream stream(&file); stream.setEncoding(TQTextStream::Latin1); stream << "BasKetNP:archive\n" << "version:0.6.1\n" // << "read-compatible:0.6.1\n" // << "write-compatible:0.6.1\n" << "preview*:" << previewSize << "\n"; // Copy the Preview File: const TQ_ULONG BUFFER_SIZE = 1024; char *buffer = new char[BUFFER_SIZE]; TQ_LONG sizeRead; TQFile previewFile(tempFolder + "preview.png"); if (previewFile.open(IO_ReadOnly)) { while ((sizeRead = previewFile.readBlock(buffer, BUFFER_SIZE)) > 0) file.writeBlock(buffer, sizeRead); } stream << "archive*:" << archiveSize << "\n"; // Copy the Archive File: TQFile archiveFile(tempDestination); if (archiveFile.open(IO_ReadOnly)) { while ((sizeRead = archiveFile.readBlock(buffer, BUFFER_SIZE)) > 0) file.writeBlock(buffer, sizeRead); } // Clean Up: delete buffer; buffer = 0; file.close(); } progress->advance(1); // Finishing finished std::cout << "Finishing finished" << std::endl; // Clean Up Everything: dir.remove(tempFolder + "preview.png"); dir.remove(tempDestination); dir.rmdir(tempFolder); } void Archive::saveBasketToArchive(Basket *basket, bool recursive, KTar *tar, TQStringList &backgrounds, const TQString &tempFolder, KProgress *progress) { // Basket need to be loaded for tags exportation. // We load it NOW so that the progress bar really reflect the state of the exportation: if (!basket->isLoaded()) { basket->load(); } TQDir dir; // Save basket data: tar->addLocalDirectory(basket->fullPath(), "baskets/" + basket->folderName()); tar->addLocalFile(basket->fullPath() + ".basket", "baskets/" + basket->folderName() + ".basket"); // The hidden files were not added // Save basket icon: TQString tempIconFile = tempFolder + "icon.png"; if (!basket->icon().isEmpty() && basket->icon() != "basket") { TQPixmap icon = kapp->iconLoader()->loadIcon(basket->icon(), TDEIcon::Small, 16, TDEIcon::DefaultState, /*path_store=*/0L, /*canReturnNull=*/true); if (!icon.isNull()) { icon.save(tempIconFile, "PNG"); TQString iconFileName = basket->icon().replace('/', '_'); tar->addLocalFile(tempIconFile, "basket-icons/" + iconFileName); } } // Save basket backgorund image: TQString imageName = basket->backgroundImageName(); if (!basket->backgroundImageName().isEmpty() && !backgrounds.contains(imageName)) { TQString backgroundPath = Global::backgroundManager->pathForImageName(imageName); if (!backgroundPath.isEmpty()) { // Save the background image: tar->addLocalFile(backgroundPath, "backgrounds/" + imageName); // Save the preview image: TQString previewPath = Global::backgroundManager->previewPathForImageName(imageName); if (!previewPath.isEmpty()) tar->addLocalFile(previewPath, "backgrounds/previews/" + imageName); // Save the configuration file: TQString configPath = backgroundPath + ".config"; if (dir.exists(configPath)) tar->addLocalFile(configPath, "backgrounds/" + imageName + ".config"); } backgrounds.append(imageName); } progress->advance(1); // Basket exportation finished std::cout << basket->basketName().local8Bit() << " finished" << std::endl; // Recursively save child baskets: BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket); if (recursive && item && item->firstChild()) { for (BasketListViewItem *child = (BasketListViewItem*) item->firstChild(); child; child = (BasketListViewItem*) child->nextSibling()) { saveBasketToArchive(child->basket(), recursive, tar, backgrounds, tempFolder, progress); } } } void Archive::listUsedTags(Basket *basket, bool recursive, TQValueList &list) { basket->listUsedTags(list); BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket); if (recursive && item->firstChild()) { for (BasketListViewItem *child = (BasketListViewItem*) item->firstChild(); child; child = (BasketListViewItem*) child->nextSibling()) { listUsedTags(child->basket(), recursive, list); } } } void Archive::open(const TQString &path) { // Create the temporar folder: TQString tempFolder = Global::savesFolder() + "temp-archive/"; TQDir dir; dir.mkdir(tempFolder); const TQ_ULONG BUFFER_SIZE = 1024; TQFile file(path); if (file.open(IO_ReadOnly)) { TQTextStream stream(&file); stream.setEncoding(TQTextStream::Latin1); TQString line = stream.readLine(); if (line != "BasKetNP:archive") { KMessageBox::error(0, i18n("This file is not a basket archive."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } TQString version; TQStringList readCompatibleVersions; TQStringList writeCompatibleVersions; while (!stream.atEnd()) { // Get Key/Value Pair From the Line to Read: line = stream.readLine(); int index = line.find(':'); TQString key; TQString value; if (index >= 0) { key = line.left(index); value = line.right(line.length() - index - 1); } else { key = line; value = ""; } if (key == "version") { version = value; } else if (key == "read-compatible") { readCompatibleVersions = TQStringList::split(value, ";"); } else if (key == "write-compatible") { writeCompatibleVersions = TQStringList::split(value, ";"); } else if (key == "preview*") { bool ok; ulong size = value.toULong(&ok); if (!ok) { KMessageBox::error(0, i18n("This file is corrupted. It can not be opened."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } // Get the preview file: //FIXME: We do not need the preview for now // TQFile previewFile(tempFolder + "preview.png"); // if (previewFile.open(IO_WriteOnly)) { char *buffer = new char[BUFFER_SIZE]; TQ_LONG sizeRead; while ((sizeRead = file.readBlock(buffer, TQMIN(BUFFER_SIZE, size))) > 0) { // previewFile.writeBlock(buffer, sizeRead); size -= sizeRead; } // previewFile.close(); delete buffer; // } } else if (key == "archive*") { if (version != "0.6.1" && readCompatibleVersions.contains("0.6.1") && !writeCompatibleVersions.contains("0.6.1")) { KMessageBox::information( 0, i18n("This file was created with a recent version of %1. " "It can be opened but not every information will be available to you. " "For instance, some notes may be missing because they are of a type only available in new versions. " "When saving the file back, consider to save it to another file, to preserve the original one.") .arg(kapp->aboutData()->programName()), i18n("Basket Archive Error") ); } if (version != "0.6.1" && !readCompatibleVersions.contains("0.6.1") && !writeCompatibleVersions.contains("0.6.1")) { KMessageBox::error( 0, i18n("This file was created with a recent version of %1. Please upgrade to a newer version to be able to open that file.") .arg(kapp->aboutData()->programName()), i18n("Basket Archive Error") ); file.close(); Tools::deleteRecursively(tempFolder); return; } bool ok; ulong size = value.toULong(&ok); if (!ok) { KMessageBox::error(0, i18n("This file is corrupted. It can not be opened."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } Global::mainWindow()->raise(); // Get the archive file: TQString tempArchive = tempFolder + "temp-archive.tar.gz"; TQFile archiveFile(tempArchive); if (archiveFile.open(IO_WriteOnly)) { char *buffer = new char[BUFFER_SIZE]; TQ_LONG sizeRead; while ((sizeRead = file.readBlock(buffer, TQMIN(BUFFER_SIZE, size))) > 0) { archiveFile.writeBlock(buffer, sizeRead); size -= sizeRead; } archiveFile.close(); delete buffer; // Extract the Archive: TQString extractionFolder = tempFolder + "extraction/"; TQDir dir; dir.mkdir(extractionFolder); KTar tar(tempArchive, "application/x-gzip"); tar.open(IO_ReadOnly); tar.directory()->copyTo(extractionFolder); tar.close(); // Import the Tags: importTagEmblems(extractionFolder); // Import and rename tag emblems BEFORE loading them! TQMap mergedStates = Tag::loadTags(extractionFolder + "tags.xml"); TQMap::Iterator it; if (mergedStates.count() > 0) { Tag::saveTags(); } // Import the Background Images: importArchivedBackgroundImages(extractionFolder); // Import the Baskets: renameBasketFolders(extractionFolder, mergedStates); } } else if (key.endsWith("*")) { // We do not know what it is, but we should read the embedded-file in order to discard it: bool ok; ulong size = value.toULong(&ok); if (!ok) { KMessageBox::error(0, i18n("This file is corrupted. It can not be opened."), i18n("Basket Archive Error")); file.close(); Tools::deleteRecursively(tempFolder); return; } // Get the archive file: char *buffer = new char[BUFFER_SIZE]; TQ_LONG sizeRead; while ((sizeRead = file.readBlock(buffer, TQMIN(BUFFER_SIZE, size))) > 0) { size -= sizeRead; } delete buffer; } else { // We do not know what it is, and we do not care. } // Analyse the Value, if Understood: } file.close(); } Tools::deleteRecursively(tempFolder); } /** * When opening a basket archive that come from another computer, * it can contains tags that use icons (emblems) that are not present on that computer. * Fortunately, basket archives contains a copy of every used icons. * This method check for every emblems and import the missing ones. * It also modify the tags.xml copy for the emblems to point to the absolute path of the impported icons. */ void Archive::importTagEmblems(const TQString &extractionFolder) { TQDomDocument *document = XMLWork::openFile("basketTags", extractionFolder + "tags.xml"); if (document == 0) return; TQDomElement docElem = document->documentElement(); TQDir dir; dir.mkdir(Global::savesFolder() + "tag-emblems/"); FormatImporter copier; // Only used to copy files synchronously TQDomNode node = docElem.firstChild(); while (!node.isNull()) { TQDomElement element = node.toElement(); if ( (!element.isNull()) && element.tagName() == "tag" ) { TQDomNode subNode = element.firstChild(); while (!subNode.isNull()) { TQDomElement subElement = subNode.toElement(); if ( (!subElement.isNull()) && subElement.tagName() == "state" ) { TQString emblemName = XMLWork::getElementText(subElement, "emblem"); if (!emblemName.isEmpty()) { TQPixmap emblem = kapp->iconLoader()->loadIcon(emblemName, TDEIcon::NoGroup, 16, TDEIcon::DefaultState, 0L, /*canReturnNull=*/true); // The icon does not exists on that computer, import it: if (emblem.isNull()) { // Of the emblem path was eg. "/home/seb/emblem.png", it was exported as "tag-emblems/_home_seb_emblem.png". // So we need to copy that image to "~/.trinity/share/apps/basket/tag-emblems/emblem.png": int slashIndex = emblemName.findRev("/"); TQString emblemFileName = (slashIndex < 0 ? emblemName : emblemName.right(slashIndex - 2)); TQString source = extractionFolder + "tag-emblems/" + emblemName.replace('/', '_'); TQString destination = Global::savesFolder() + "tag-emblems/" + emblemFileName; if (!dir.exists(destination)) copier.copyFolder(source, destination); // Replace the emblem path in the tags.xml copy: TQDomElement emblemElement = XMLWork::getElement(subElement, "emblem"); subElement.removeChild(emblemElement); XMLWork::addElement(*document, subElement, "emblem", destination); } } } subNode = subNode.nextSibling(); } } node = node.nextSibling(); } Basket::safelySaveToFile(extractionFolder + "tags.xml", document->toString()); } void Archive::importArchivedBackgroundImages(const TQString &extractionFolder) { FormatImporter copier; // Only used to copy files synchronously TQString destFolder = TDEGlobal::dirs()->saveLocation("data", "basket/backgrounds/"); TQDir dir(extractionFolder + "backgrounds/", /*nameFilder=*/"*.png", /*sortSpec=*/TQDir::Name | TQDir::IgnoreCase, /*filterSpec=*/TQDir::Files | TQDir::NoSymLinks); TQStringList files = dir.entryList(); for (TQStringList::Iterator it = files.begin(); it != files.end(); ++it) { TQString image = *it; if (!Global::backgroundManager->exists(image)) { // Copy images: TQString imageSource = extractionFolder + "backgrounds/" + image; TQString imageDest = destFolder + image; copier.copyFolder(imageSource, imageDest); // Copy configuration file: TQString configSource = extractionFolder + "backgrounds/" + image + ".config"; TQString configDest = destFolder + image; if (dir.exists(configSource)) copier.copyFolder(configSource, configDest); // Copy preview: TQString previewSource = extractionFolder + "backgrounds/previews/" + image; TQString previewDest = destFolder + "previews/" + image; if (dir.exists(previewSource)) { dir.mkdir(destFolder + "previews/"); // Make sure the folder exists! copier.copyFolder(previewSource, previewDest); } // Append image to database: Global::backgroundManager->addImage(imageDest); } } } void Archive::renameBasketFolders(const TQString &extractionFolder, TQMap &mergedStates) { TQDomDocument *doc = XMLWork::openFile("basketTree", extractionFolder + "baskets/baskets.xml"); if (doc != 0) { TQMap folderMap; TQDomElement docElem = doc->documentElement(); TQDomNode node = docElem.firstChild(); renameBasketFolder(extractionFolder, node, folderMap, mergedStates); loadExtractedBaskets(extractionFolder, node, folderMap, 0); } } void Archive::renameBasketFolder(const TQString &extractionFolder, TQDomNode &basketNode, TQMap &folderMap, TQMap &mergedStates) { TQDomNode n = basketNode; while ( ! n.isNull() ) { TQDomElement element = n.toElement(); if ( (!element.isNull()) && element.tagName() == "basket" ) { TQString folderName = element.attribute("folderName"); if (!folderName.isEmpty()) { // Find a folder name: TQString newFolderName = BasketFactory::newFolderName(); folderMap[folderName] = newFolderName; // Reserve the folder name: TQDir dir; dir.mkdir(Global::basketsFolder() + newFolderName); // Rename the merged tag ids: // if (mergedStates.count() > 0) { renameMergedStatesAndBasketIcon(extractionFolder + "baskets/" + folderName + ".basket", mergedStates, extractionFolder); // } // Child baskets: TQDomNode node = element.firstChild(); renameBasketFolder(extractionFolder, node, folderMap, mergedStates); } } n = n.nextSibling(); } } void Archive::renameMergedStatesAndBasketIcon(const TQString &fullPath, TQMap &mergedStates, const TQString &extractionFolder) { TQDomDocument *doc = XMLWork::openFile("basket", fullPath); if (doc == 0) return; TQDomElement docElem = doc->documentElement(); TQDomElement properties = XMLWork::getElement(docElem, "properties"); importBasketIcon(properties, extractionFolder); TQDomElement notes = XMLWork::getElement(docElem, "notes"); if (mergedStates.count() > 0) renameMergedStates(notes, mergedStates); Basket::safelySaveToFile(fullPath, /*"\n" + */doc->toString()); } void Archive::importBasketIcon(TQDomElement properties, const TQString &extractionFolder) { TQString iconName = XMLWork::getElementText(properties, "icon"); if (!iconName.isEmpty() && iconName != "basket") { TQPixmap icon = kapp->iconLoader()->loadIcon(iconName, TDEIcon::NoGroup, 16, TDEIcon::DefaultState, 0L, /*canReturnNull=*/true); // The icon does not exists on that computer, import it: if (icon.isNull()) { TQDir dir; dir.mkdir(Global::savesFolder() + "basket-icons/"); FormatImporter copier; // Only used to copy files synchronously // Of the icon path was eg. "/home/seb/icon.png", it was exported as "basket-icons/_home_seb_icon.png". // So we need to copy that image to "~/.trinity/share/apps/basket/basket-icons/icon.png": int slashIndex = iconName.findRev("/"); TQString iconFileName = (slashIndex < 0 ? iconName : iconName.right(slashIndex - 2)); TQString source = extractionFolder + "basket-icons/" + iconName.replace('/', '_'); TQString destination = Global::savesFolder() + "basket-icons/" + iconFileName; if (!dir.exists(destination)) copier.copyFolder(source, destination); // Replace the emblem path in the tags.xml copy: TQDomElement iconElement = XMLWork::getElement(properties, "icon"); properties.removeChild(iconElement); TQDomDocument document = properties.ownerDocument(); XMLWork::addElement(document, properties, "icon", destination); } } } void Archive::renameMergedStates(TQDomNode notes, TQMap &mergedStates) { TQDomNode n = notes.firstChild(); while ( ! n.isNull() ) { TQDomElement element = n.toElement(); if (!element.isNull()) { if (element.tagName() == "group" ) { renameMergedStates(n, mergedStates); } else if (element.tagName() == "note") { TQString tags = XMLWork::getElementText(element, "tags"); if (!tags.isEmpty()) { TQStringList tagNames = TQStringList::split(";", tags); for (TQStringList::Iterator it = tagNames.begin(); it != tagNames.end(); ++it) { TQString &tag = *it; if (mergedStates.contains(tag)) { tag = mergedStates[tag]; } } TQString newTags = tagNames.join(";"); TQDomElement tagsElement = XMLWork::getElement(element, "tags"); element.removeChild(tagsElement); TQDomDocument document = element.ownerDocument(); XMLWork::addElement(document, element, "tags", newTags); } } } n = n.nextSibling(); } } void Archive::loadExtractedBaskets(const TQString &extractionFolder, TQDomNode &basketNode, TQMap &folderMap, Basket *parent) { bool basketSetAsCurrent = (parent != 0); TQDomNode n = basketNode; while ( ! n.isNull() ) { TQDomElement element = n.toElement(); if ( (!element.isNull()) && element.tagName() == "basket" ) { TQString folderName = element.attribute("folderName"); if (!folderName.isEmpty()) { // Move the basket folder to its destination, while renaming it uniquely: TQString newFolderName = folderMap[folderName]; FormatImporter copier; // The folder has been "reserved" by creating it. Avoid asking the user to override: TQDir dir; dir.rmdir(Global::basketsFolder() + newFolderName); copier.moveFolder(extractionFolder + "baskets/" + folderName, Global::basketsFolder() + newFolderName); // Append and load the basket in the tree: Basket *basket = Global::bnpView->loadBasket(newFolderName); BasketListViewItem *basketItem = Global::bnpView->appendBasket(basket, (basket && parent ? Global::bnpView->listViewItemForBasket(parent) : 0)); basketItem->setOpen(!XMLWork::trueOrFalse(element.attribute("folded", "false"), false)); TQDomElement properties = XMLWork::getElement(element, "properties"); importBasketIcon(properties, extractionFolder); // Rename the icon fileName if necessary basket->loadProperties(properties); // Open the first basket of the archive: if (!basketSetAsCurrent) { Global::bnpView->setCurrentBasket(basket); basketSetAsCurrent = true; } TQDomNode node = element.firstChild(); loadExtractedBaskets(extractionFolder, node, folderMap, basket); } } n = n.nextSibling(); } }