File indexing completed on 2024-05-12 05:10:11

0001 /***************************************************************************
0002     Copyright (C) 2005-2022 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 "gcstarimporter.h"
0026 #include "../collections/videocollection.h"
0027 #include "../utils/string_utils.h"
0028 #include "../utils/datafileregistry.h"
0029 #include "../images/imagefactory.h"
0030 #include "../borrower.h"
0031 #include "../fieldformat.h"
0032 #include "xslthandler.h"
0033 #include "tellicoimporter.h"
0034 
0035 #include <KLocalizedString>
0036 
0037 #include <QTextCodec>
0038 #include <QTextStream>
0039 
0040 #define CHECKLIMITS(n) if(values.count() <= n) continue
0041 
0042 using Tellico::Import::GCstarImporter;
0043 
0044 GCstarImporter::GCstarImporter(const QUrl& url_) : TextImporter(url_, true), m_cancelled(false), m_relativeImageLinks(false) {
0045 }
0046 
0047 GCstarImporter::GCstarImporter(const QString& text_) : TextImporter(text_), m_cancelled(false), m_relativeImageLinks(false) {
0048 }
0049 
0050 bool GCstarImporter::canImport(int type) const {
0051   return type == Data::Collection::Video
0052       || type == Data::Collection::Book
0053       || type == Data::Collection::ComicBook
0054       || type == Data::Collection::Album
0055       || type == Data::Collection::Game
0056       || type == Data::Collection::Wine
0057       || type == Data::Collection::Coin
0058       || type == Data::Collection::BoardGame;
0059 }
0060 
0061 Tellico::Data::CollPtr GCstarImporter::collection() {
0062   if(m_coll) {
0063     return m_coll;
0064   }
0065 
0066   emit signalTotalSteps(this, 100);
0067 
0068   QString str = text();
0069   QTextStream t(&str);
0070   QString line = t.readLine();
0071   if(line.startsWith(QLatin1String("GCfilms"))) {
0072     readGCfilms(str);
0073   } else {
0074     // need to reparse the string if it's in utf-8
0075     if(line.toLower().indexOf(QLatin1String("utf-8")) > 0) {
0076       str = QString::fromUtf8(str.toLocal8Bit());
0077     }
0078     readGCstar(str);
0079   }
0080   return m_coll;
0081 }
0082 
0083 void GCstarImporter::readGCfilms(const QString& text_) {
0084   m_coll = new Data::VideoCollection(true);
0085   bool hasURL = false;
0086   if(m_coll->hasField(QStringLiteral("url"))) {
0087     hasURL = m_coll->fieldByName(QStringLiteral("url"))->type() == Data::Field::URL;
0088   } else {
0089     Data::FieldPtr field(new Data::Field(QStringLiteral("url"), i18n("URL"), Data::Field::URL));
0090     field->setCategory(i18n("General"));
0091     m_coll->addField(field);
0092     hasURL = true;
0093   }
0094 
0095   bool convertUTF8 = false;
0096   QHash<QString, Data::BorrowerPtr> borrowers;
0097   static const QRegularExpression rx(QLatin1String("\\s*,\\s*"));
0098   static const QRegularExpression year(QLatin1String("\\d{4}"));
0099   static const QRegularExpression runTimeHr(QLatin1String("(\\d+)\\s?hr?"));
0100   static const QRegularExpression runTimeMin(QLatin1String("(\\d+)\\s?mi?n?"));
0101 
0102   bool gotFirstLine = false;
0103 
0104   QString tmp = text_;
0105   QTextStream t(&tmp);
0106 
0107   const uint length = text_.length();
0108   const uint stepSize = qMax(s_stepSize, length/100);
0109   const bool showProgress = options() & ImportProgress;
0110 
0111   emit signalTotalSteps(this, length);
0112 
0113   uint j = 0;
0114   for(QString line = t.readLine(); !m_cancelled && !line.isNull(); line = t.readLine(), j += line.length()) {
0115     // string was wrongly converted
0116     if(convertUTF8) {
0117       line = QString::fromUtf8(line.toLocal8Bit());
0118     }
0119     QStringList values = line.split(QLatin1Char('|'));
0120     if(values.empty()) {
0121       continue;
0122     }
0123 
0124     if(!gotFirstLine) {
0125       if(values[0] != QLatin1String("GCfilms")) {
0126         setStatusMessage(i18n("<qt>The file is not a valid GCstar data file.</qt>"));
0127         m_coll = nullptr;
0128         return;
0129       }
0130       if(values.size() > 2 && values[2] == QLatin1String("UTF8")) {
0131         // if locale encoding isn't utf8, need to do a reconversion
0132         QTextCodec* codec = QTextCodec::codecForLocale();
0133         if(codec->name().toLower().indexOf("utf-8") == -1) {
0134           convertUTF8 = true;
0135         }
0136       }
0137       gotFirstLine = true;
0138       continue;
0139     }
0140 
0141     bool ok;
0142 
0143     Data::EntryPtr entry(new Data::Entry(m_coll));
0144     entry->setId(Tellico::toUInt(values[0], &ok));
0145     entry->setField(QStringLiteral("title"), values[1]);
0146     QRegularExpressionMatch yearMatch = year.match(values[2]);
0147     if(yearMatch.hasMatch()) {
0148       entry->setField(QStringLiteral("year"), yearMatch.captured());
0149     }
0150 
0151     uint time = 0;
0152     QRegularExpressionMatch runTimeMatch = runTimeHr.match(values[3]);
0153     if(runTimeMatch.hasMatch()) {
0154       time = Tellico::toUInt(runTimeMatch.captured(1), &ok) * 60;
0155     }
0156     runTimeMatch = runTimeMin.match(values[3]);
0157     if(runTimeMatch.hasMatch()) {
0158       time += Tellico::toUInt(runTimeMatch.captured(1), &ok);
0159     }
0160     if(time > 0) {
0161       entry->setField(QStringLiteral("running-time"),  QString::number(time));
0162     }
0163 
0164     entry->setField(QStringLiteral("director"),    splitJoin(rx, values[4]));
0165     entry->setField(QStringLiteral("nationality"), splitJoin(rx, values[5]));
0166     entry->setField(QStringLiteral("genre"),       splitJoin(rx, values[6]));
0167     QUrl u = QUrl(values[7]);
0168     if(!u.isEmpty()) {
0169       QString id = ImageFactory::addImage(u, true /* quiet */);
0170       if(!id.isEmpty()) {
0171         entry->setField(QStringLiteral("cover"), id);
0172       }
0173     }
0174     entry->setField(QStringLiteral("cast"),  splitJoin(rx, values[8]));
0175     // values[9] is the original title
0176     entry->setField(QStringLiteral("plot"),  values[10]);
0177     if(hasURL) {
0178       entry->setField(QStringLiteral("url"), values[11]);
0179     }
0180 
0181     CHECKLIMITS(12);
0182 
0183     // values[12] is whether the film has been viewed or not
0184     entry->setField(QStringLiteral("medium"), values[13]);
0185     // values[14] is number of DVDS?
0186     // values[15] is place?
0187     // gcfilms's ratings go 0-10, just divide by two
0188     entry->setField(QStringLiteral("rating"), QString::number(int(Tellico::toUInt(values[16], &ok)/2)));
0189     entry->setField(QStringLiteral("comments"), values[17]);
0190 
0191     CHECKLIMITS(18);
0192 
0193     QStringList s = values[18].split(QLatin1Char(','));
0194     QStringList tracks, langs;
0195     foreach(const QString& value, s) {
0196       langs << value.section(QLatin1Char(';'), 0, 0);
0197       tracks << value.section(QLatin1Char(';'), 1, 1);
0198     }
0199     entry->setField(QStringLiteral("language"), langs.join(FieldFormat::delimiterString()));
0200     entry->setField(QStringLiteral("audio-track"), tracks.join(FieldFormat::delimiterString()));
0201 
0202     entry->setField(QStringLiteral("subtitle"), splitJoin(rx, values[19]));
0203 
0204     CHECKLIMITS(20);
0205 
0206     // values[20] is borrower name
0207     if(!values[20].isEmpty()) {
0208       QString tmp = values[20];
0209       Data::BorrowerPtr b = borrowers[tmp];
0210       if(!b) {
0211         b = new Data::Borrower(tmp, QString());
0212         borrowers.insert(tmp, b);
0213       }
0214       // values[21] is loan date
0215       if(!values[21].isEmpty()) {
0216         tmp = values[21]; // assume date is dd/mm/yyyy
0217         int d = Tellico::toUInt(tmp.section(QLatin1Char('/'), 0, 0), &ok);
0218         int m = Tellico::toUInt(tmp.section(QLatin1Char('/'), 1, 1), &ok);
0219         int y = Tellico::toUInt(tmp.section(QLatin1Char('/'), 2, 2), &ok);
0220         b->addLoan(Data::LoanPtr(new Data::Loan(entry, QDate(y, m, d), QDate(), QString())));
0221         entry->setField(QStringLiteral("loaned"), QStringLiteral("true"));
0222       }
0223     }
0224     // values[22] is history ?
0225     // for certification, only thing we can do is assume default american ratings
0226     // they're not translated one for one
0227     CHECKLIMITS(23);
0228 
0229     int age = Tellico::toUInt(values[23], &ok);
0230     if(age < 2) {
0231       entry->setField(QStringLiteral("certification"), QStringLiteral("U (USA)"));
0232     } else if(age < 3) {
0233       entry->setField(QStringLiteral("certification"), QStringLiteral("G (USA)"));
0234     } else if(age < 6) {
0235       entry->setField(QStringLiteral("certification"), QStringLiteral("PG (USA)"));
0236     } else if(age < 14) {
0237       entry->setField(QStringLiteral("certification"), QStringLiteral("PG-13 (USA)"));
0238     } else {
0239       entry->setField(QStringLiteral("certification"), QStringLiteral("R (USA)"));
0240     }
0241 
0242     m_coll->addEntries(entry);
0243 
0244     if(showProgress && j%stepSize == 0) {
0245       emit signalProgress(this, j);
0246     }
0247   }
0248 
0249   if(m_cancelled) {
0250     m_coll = nullptr;
0251     return;
0252   }
0253 
0254   for(QHash<QString, Data::BorrowerPtr>::Iterator it = borrowers.begin(); it != borrowers.end(); ++it) {
0255     if(!it.value()->isEmpty()) {
0256       m_coll->addBorrower(it.value());
0257     }
0258   }
0259 }
0260 
0261 void GCstarImporter::readGCstar(const QString& text_) {
0262   QString xsltFile = DataFileRegistry::self()->locate(QStringLiteral("gcstar2tellico.xsl"));
0263   XSLTHandler handler(QUrl::fromLocalFile(xsltFile));
0264   if(!handler.isValid()) {
0265     setStatusMessage(i18n("Tellico encountered an error in XSLT processing."));
0266     return;
0267   }
0268 
0269   const QString str = handler.applyStylesheet(text_);
0270 
0271   if(str.isEmpty()) {
0272     setStatusMessage(i18n("<qt>The file is not a valid GCstar data file.</qt>"));
0273     return;
0274   }
0275 
0276   Import::TellicoImporter imp(str);
0277   if(m_relativeImageLinks) {
0278     imp.setBaseUrl(url()); /// empty base url is ok
0279   }
0280   m_coll = imp.collection();
0281   setStatusMessage(imp.statusMessage());
0282 }
0283 
0284 inline
0285 QString GCstarImporter::splitJoin(const QRegularExpression& rx, const QString& s) {
0286 #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
0287   return s.split(rx, QString::SkipEmptyParts).join(FieldFormat::delimiterString());
0288 #else
0289   return s.split(rx, Qt::SkipEmptyParts).join(FieldFormat::delimiterString());
0290 #endif
0291 }
0292 
0293 void GCstarImporter::slotCancel() {
0294   m_cancelled = true;
0295 }
0296 
0297 #undef CHECKLIMITS