File indexing completed on 2024-05-12 05:10:07
0001 /*************************************************************************** 0002 Copyright (C) 2003-2020 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "alexandriaexporter.h" 0026 #include "../collection.h" 0027 #include "../fieldformat.h" 0028 #include "../images/imagefactory.h" 0029 #include "../images/image.h" 0030 #include "../utils/string_utils.h" 0031 #include "../progressmanager.h" 0032 #include "../utils/guiproxy.h" 0033 #include "../tellico_debug.h" 0034 0035 #include <KLocalizedString> 0036 #include <KMessageBox> 0037 0038 #include <QDir> 0039 #include <QTextStream> 0040 #include <QApplication> 0041 0042 namespace { 0043 static const int ALEXANDRIA_MAX_SIZE_SMALL = 60; 0044 static const int ALEXANDRIA_MAX_SIZE_MEDIUM = 140; 0045 } 0046 0047 using namespace Tellico; 0048 using Tellico::Export::AlexandriaExporter; 0049 0050 AlexandriaExporter::AlexandriaExporter(Data::CollPtr coll_) : Exporter(coll_) { 0051 } 0052 0053 QString AlexandriaExporter::escapeText(const QString& str_) { 0054 // no control characters at all which is more strict than XML (which is what string_utils::removeControlCharacters has) 0055 QString result; 0056 result.reserve(str_.size()); 0057 for(int i = 0; i < str_.size(); ++i) { 0058 const ushort c = str_.at(i).unicode(); 0059 if((c > 0x1F && c < 0x7F) || (c > 0xA0)) { 0060 result += str_.at(i); 0061 } 0062 } 0063 result.replace(QLatin1String("\""), QLatin1String("\\\"")); 0064 return result; 0065 } 0066 0067 QString AlexandriaExporter::formatString() const { 0068 return QStringLiteral("Alexandria"); 0069 } 0070 0071 bool AlexandriaExporter::exec() { 0072 Data::CollPtr coll = collection(); 0073 if(!coll || (coll->type() != Data::Collection::Book && coll->type() != Data::Collection::Bibtex)) { 0074 myLog() << "bad collection"; 0075 return false; 0076 } 0077 0078 const QString alexDirName = QStringLiteral(".alexandria"); 0079 0080 // create if necessary 0081 const QUrl u = url(); 0082 QDir libraryDir; 0083 if(u.isEmpty()) { 0084 libraryDir = QDir::home(); 0085 } else { 0086 if(u.isLocalFile()) { 0087 if(!libraryDir.cd(u.toLocalFile())) { 0088 myWarning() << "can't change to directory:" << u.path(); 0089 return false; 0090 } 0091 } else { 0092 myWarning() << "can't write to remote directory"; 0093 return false; 0094 } 0095 } 0096 0097 if(!libraryDir.cd(alexDirName)) { 0098 if(!libraryDir.mkdir(alexDirName) || !libraryDir.cd(alexDirName)) { 0099 myLog() << "can't locate directory"; 0100 return false; 0101 } 0102 } 0103 0104 // the collection title is the name of the directory to create 0105 if(libraryDir.cd(coll->title())) { 0106 int ret = KMessageBox::warningContinueCancel(GUI::Proxy::widget(), 0107 i18n("<qt>An Alexandria library called <i>%1</i> already exists. " 0108 "Any existing books in that library could be overwritten.</qt>", 0109 coll->title())); 0110 if(ret == KMessageBox::Cancel) { 0111 return false; 0112 } 0113 } else if(!libraryDir.mkdir(coll->title()) || !libraryDir.cd(coll->title())) { 0114 return false; // could not create and cd to the dir 0115 } 0116 0117 ProgressItem& item = ProgressManager::self()->newProgressItem(this, QString(), false); 0118 item.setTotalSteps(entries().count()); 0119 ProgressItem::Done done(this); 0120 const uint stepSize = qMax(1, entries().count()/100); 0121 const bool showProgress = options() & ExportProgress; 0122 0123 bool success = true; 0124 uint j = 0; 0125 foreach(const Data::EntryPtr& entry, entries()) { 0126 success &= writeFile(libraryDir, entry); 0127 if(showProgress && j%stepSize == 0) { 0128 item.setProgress(j); 0129 qApp->processEvents(); 0130 } 0131 ++j; 0132 } 0133 return success; 0134 } 0135 0136 // this isn't true YAML export, of course 0137 // everything is put between quotes except for the rating, just to be sure it's interpreted as a string 0138 bool AlexandriaExporter::writeFile(const QDir& dir_, Tellico::Data::EntryPtr entry_) { 0139 // the filename is the isbn without dashes, followed by .yaml 0140 QString isbn = entry_->field(QStringLiteral("isbn")); 0141 if(isbn.isEmpty()) { 0142 return false; // can't write it since Alexandria uses isbn as name of file 0143 } 0144 isbn.remove(QLatin1Char('-')); // remove dashes 0145 0146 QFile file(dir_.absolutePath() + QDir::separator() + isbn + QLatin1String(".yaml")); 0147 if(!file.open(QIODevice::WriteOnly)) { 0148 return false; 0149 } 0150 0151 // do we format? 0152 FieldFormat::Request format = (options() & Export::ExportFormatted ? 0153 FieldFormat::ForceFormat : 0154 FieldFormat::AsIsFormat); 0155 0156 QTextStream ts(&file); 0157 // alexandria uses utf-8 all the time 0158 ts.setCodec("UTF-8"); 0159 ts << "--- !ruby/object:Alexandria::Book\n"; 0160 ts << "authors:\n"; 0161 QStringList authors = FieldFormat::splitValue(entry_->formattedField(QStringLiteral("author"), format)); 0162 for(QStringList::Iterator it = authors.begin(); it != authors.end(); ++it) { 0163 ts << " - " << escapeText(*it) << "\n"; 0164 } 0165 // Alexandria crashes when no authors, and uses n/a when none 0166 if(authors.count() == 0) { 0167 ts << " - n/a\n"; 0168 } 0169 0170 QString tmp = entry_->title(format); 0171 ts << "title: \"" << escapeText(tmp) << "\"\n"; 0172 0173 // Alexandria refers to the binding as the edition 0174 tmp = entry_->formattedField(QStringLiteral("binding"), format); 0175 ts << "edition: \"" << escapeText(tmp) << "\"\n"; 0176 0177 // sometimes Alexandria interprets the isbn as a number instead of a string 0178 // I have no idea how to debug ruby, so err on safe side and add quotes 0179 ts << "isbn: \"" << isbn << "\"\n"; 0180 0181 static const QRegularExpression rx(QLatin1String("<br/?>"), QRegularExpression::CaseInsensitiveOption); 0182 tmp = entry_->formattedField(QStringLiteral("comments"), format); 0183 tmp.replace(rx, QStringLiteral("\n")); 0184 ts << "notes: |-\n"; 0185 foreach(const QString& line, tmp.split(QLatin1Char('\n'))) { 0186 ts << " " << escapeText(line) << "\n"; 0187 } 0188 0189 tmp = entry_->formattedField(QStringLiteral("publisher"), format); 0190 // publisher uses n/a when empty 0191 ts << "publisher: \"" << (tmp.isEmpty() ? QStringLiteral("n/a") : escapeText(tmp)) << "\"\n"; 0192 0193 tmp = entry_->formattedField(QStringLiteral("pub_year"), format); 0194 if(!tmp.isEmpty()) { 0195 ts << "publishing_year: \"" << escapeText(tmp) << "\"\n"; 0196 } 0197 0198 tmp = entry_->field(QStringLiteral("rating")); 0199 bool ok; 0200 int rating = Tellico::toUInt(tmp, &ok); 0201 if(ok) { 0202 ts << "rating: " << rating << "\n"; 0203 } 0204 0205 tmp = entry_->field(QStringLiteral("read")); 0206 if(!tmp.isEmpty()) { 0207 ts << "redd: true\n"; 0208 } 0209 0210 file.close(); 0211 0212 QString cover = entry_->field(QStringLiteral("cover")); 0213 if(cover.isEmpty() || !(options() & Export::ExportImages)) { 0214 return true; // all done 0215 } 0216 0217 QImage img1(ImageFactory::imageById(cover)); 0218 QImage img2; 0219 QString filename = dir_.absolutePath() + QDir::separator() + isbn; 0220 if(img1.height() > ALEXANDRIA_MAX_SIZE_SMALL) { 0221 if(img1.height() > ALEXANDRIA_MAX_SIZE_MEDIUM) { // limit maximum size 0222 img1 = img1.scaled(ALEXANDRIA_MAX_SIZE_MEDIUM, ALEXANDRIA_MAX_SIZE_MEDIUM, Qt::KeepAspectRatio); 0223 } 0224 img2 = img1.scaled(ALEXANDRIA_MAX_SIZE_SMALL, ALEXANDRIA_MAX_SIZE_SMALL, Qt::KeepAspectRatio); 0225 } else { 0226 // img2 is the small image 0227 img2 = img1; 0228 img1 = QImage(); 0229 } 0230 return (img1.isNull() || img1.save(filename + QLatin1String("_medium.jpg"), "JPEG")) 0231 && img2.save(filename + QLatin1String("_small.jpg"), "JPEG"); 0232 }