File indexing completed on 2024-05-12 16:46:03
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 * In addition, as a special exception, the author gives permission to * 0024 * link the code of this program with the OpenSSL library released by * 0025 * the OpenSSL Project (or with modified versions of OpenSSL that use * 0026 * the same license as OpenSSL), and distribute linked combinations * 0027 * including the two. You must obey the GNU General Public License in * 0028 * all respects for all of the code used other than OpenSSL. If you * 0029 * modify this file, you may extend this exception to your version of * 0030 * the file, but you are not obligated to do so. If you do not wish to * 0031 * do so, delete this exception statement from your version. * 0032 * * 0033 ***************************************************************************/ 0034 0035 #include <config.h> 0036 0037 #include "z3950fetcher.h" 0038 #include "z3950connection.h" 0039 #include "../collection.h" 0040 #include "../fieldformat.h" 0041 #include "../translators/xslthandler.h" 0042 #include "../translators/tellicoimporter.h" 0043 #include "../translators/grs1importer.h" 0044 #include "../translators/adsimporter.h" 0045 #include "../gui/lineedit.h" 0046 #include "../gui/combobox.h" 0047 #include "../utils/isbnvalidator.h" 0048 #include "../utils/lccnvalidator.h" 0049 #include "../utils/datafileregistry.h" 0050 #include "../tellico_debug.h" 0051 0052 #include <KLocalizedString> 0053 #include <KConfigGroup> 0054 #include <KComboBox> 0055 #include <KAcceleratorManager> 0056 #include <KSeparator> 0057 #include <KConfig> 0058 0059 #include <QSpinBox> 0060 #include <QFile> 0061 #include <QLabel> 0062 #include <QDomDocument> 0063 #include <QTextStream> 0064 #include <QTextCodec> 0065 #include <QGridLayout> 0066 #include <QIcon> 0067 0068 namespace { 0069 static const int Z3950_DEFAULT_PORT = 210; 0070 static const char* Z3950_DEFAULT_ESN = "F"; 0071 } 0072 0073 using namespace Tellico; 0074 using Tellico::Fetch::Z3950Fetcher; 0075 0076 Z3950Fetcher::Z3950Fetcher(QObject* parent_) 0077 : Fetcher(parent_), m_conn(nullptr), m_port(Z3950_DEFAULT_PORT), m_esn(QLatin1String(Z3950_DEFAULT_ESN)), 0078 m_started(false), m_done(true), m_MARC21XMLHandler(nullptr), 0079 m_UNIMARCXMLHandler(nullptr), m_MODSHandler(nullptr) { 0080 } 0081 0082 Z3950Fetcher::Z3950Fetcher(QObject* parent_, const QString& preset_) 0083 : Fetcher(parent_), m_conn(nullptr), m_port(Z3950_DEFAULT_PORT), m_started(false), m_done(true), m_preset(preset_), 0084 m_MARC21XMLHandler(nullptr), m_UNIMARCXMLHandler(nullptr), m_MODSHandler(nullptr) { 0085 QString serverFile = DataFileRegistry::self()->locate(QStringLiteral("z3950-servers.cfg")); 0086 if(!serverFile.isEmpty()) { 0087 KConfig serverConfig(serverFile, KConfig::SimpleConfig); 0088 const QStringList servers = serverConfig.groupList(); 0089 for(QStringList::ConstIterator server = servers.begin(); server != servers.end(); ++server) { 0090 if(*server == m_preset) { 0091 KConfigGroup cfg(&serverConfig, *server); 0092 m_host = cfg.readEntry("Host"); 0093 m_port = cfg.readEntry("Port", Z3950_DEFAULT_PORT); 0094 m_dbname = cfg.readEntry("Database"); 0095 m_queryCharSet = cfg.readEntry("Charset"); 0096 m_responseCharSet = cfg.readEntry("CharsetResponse"); 0097 m_syntax = cfg.readEntry("Syntax"); 0098 m_user = cfg.readEntry("User"); 0099 m_password = cfg.readEntry("Password"); 0100 } 0101 } 0102 } else { 0103 myDebug() << "z3950-servers.cfg not found"; 0104 } 0105 } 0106 0107 Z3950Fetcher::Z3950Fetcher(QObject* parent_, const QString& host_, int port_, 0108 const QString& dbName_, const QString& syntax_) 0109 : Fetcher(parent_), m_conn(nullptr), m_host(host_), m_port(port_), m_dbname(dbName_) 0110 , m_syntax(syntax_), m_esn(QLatin1String(Z3950_DEFAULT_ESN)) 0111 , m_started(false), m_done(true), m_MARC21XMLHandler(nullptr) 0112 , m_UNIMARCXMLHandler(nullptr), m_MODSHandler(nullptr) { 0113 } 0114 0115 Z3950Fetcher::~Z3950Fetcher() { 0116 delete m_MARC21XMLHandler; 0117 m_MARC21XMLHandler = nullptr; 0118 delete m_UNIMARCXMLHandler; 0119 m_UNIMARCXMLHandler = nullptr; 0120 delete m_MODSHandler; 0121 m_MODSHandler = nullptr; 0122 0123 if(m_conn) { 0124 m_conn->wait(); 0125 m_conn->deleteLater(); 0126 m_conn = nullptr; 0127 } 0128 } 0129 0130 QString Z3950Fetcher::source() const { 0131 return m_name.isEmpty() ? defaultName() : m_name; 0132 } 0133 0134 // No UPC or Raw for now. 0135 bool Z3950Fetcher::canSearch(Fetch::FetchKey k) const { 0136 return k == Title || k == Person || k == ISBN || k == Keyword || k == LCCN; 0137 } 0138 0139 bool Z3950Fetcher::canFetch(int type) const { 0140 return type == Data::Collection::Book || type == Data::Collection::Bibtex; 0141 } 0142 0143 void Z3950Fetcher::readConfigHook(const KConfigGroup& config_) { 0144 QString preset = config_.readEntry("Preset"); 0145 if(preset.isEmpty()) { 0146 m_host = config_.readEntry("Host"); 0147 int p = config_.readEntry("Port", Z3950_DEFAULT_PORT); 0148 if(p > 0) { 0149 m_port = p; 0150 } 0151 m_dbname = config_.readEntry("Database"); 0152 m_queryCharSet = config_.readEntry("Charset"); 0153 m_responseCharSet = config_.readEntry("CharsetResponse"); 0154 if(m_responseCharSet.isEmpty()) { 0155 m_responseCharSet = m_queryCharSet; 0156 } 0157 m_syntax = config_.readEntry("Syntax"); 0158 m_user = config_.readEntry("User"); 0159 m_password = config_.readEntry("Password"); 0160 } else { 0161 m_preset = preset; 0162 QString serverFile = DataFileRegistry::self()->locate(QStringLiteral("z3950-servers.cfg")); 0163 if(!serverFile.isEmpty()) { 0164 KConfig serverConfig(serverFile, KConfig::SimpleConfig); 0165 const QStringList servers = serverConfig.groupList(); 0166 for(QStringList::ConstIterator server = servers.begin(); server != servers.end(); ++server) { 0167 if(*server == preset) { 0168 KConfigGroup cfg(&serverConfig, *server); 0169 // const QString name = cfg.readEntry("Name"); 0170 m_host = cfg.readEntry("Host"); 0171 m_port = cfg.readEntry("Port", Z3950_DEFAULT_PORT); 0172 m_dbname = cfg.readEntry("Database"); 0173 m_queryCharSet = cfg.readEntry("Charset"); 0174 m_syntax = cfg.readEntry("Syntax"); 0175 m_user = cfg.readEntry("User"); 0176 m_password = cfg.readEntry("Password"); 0177 } 0178 } 0179 } else { 0180 myDebug() << "z3950-servers.cfg not found"; 0181 } 0182 } 0183 } 0184 0185 void Z3950Fetcher::saveConfigHook(KConfigGroup& config_) { 0186 config_.writeEntry("Syntax", m_syntax); 0187 } 0188 0189 void Z3950Fetcher::search() { 0190 #ifdef HAVE_YAZ 0191 m_started = true; 0192 m_done = false; 0193 if(m_host.isEmpty() || m_dbname.isEmpty()) { 0194 myDebug() << "settings are not set!"; 0195 stop(); 0196 return; 0197 } 0198 m_started = true; 0199 0200 QString svalue = request().value(); 0201 QRegExp rx1(QLatin1String("^['\"].*\\1$")); 0202 if(!rx1.exactMatch(svalue)) { 0203 svalue = QLatin1Char('"') + svalue + QLatin1Char('"'); 0204 } 0205 0206 switch(request().key()) { 0207 case Title: 0208 m_pqn = QLatin1String("@attr 1=4 ") + svalue; 0209 break; 0210 case Person: 0211 // m_pqn = QLatin1String("@or "); 0212 // m_pqn += QLatin1String("@attr 1=1 \"") + request().value() + QLatin1Char('"'); 0213 m_pqn = QLatin1String(" @attr 1=1003 ") + svalue; 0214 break; 0215 case ISBN: 0216 { 0217 m_pqn.clear(); 0218 QString s = request().value(); 0219 s.remove(QLatin1Char('-')); 0220 QStringList isbnList = FieldFormat::splitValue(s); 0221 // also search for isbn10 values 0222 for(QStringList::Iterator it = isbnList.begin(); it != isbnList.end(); ++it) { 0223 if((*it).startsWith(QLatin1String("978"))) { 0224 QString isbn10 = ISBNValidator::isbn10(*it); 0225 isbn10.remove(QLatin1Char('-')); 0226 it = isbnList.insert(it, isbn10); 0227 ++it; 0228 } 0229 } 0230 const int count = isbnList.count(); 0231 if(count > 1) { 0232 m_pqn = QStringLiteral("@or "); 0233 } 0234 for(int i = 0; i < count; ++i) { 0235 m_pqn += QLatin1String(" @attr 1=7 ") + isbnList.at(i); 0236 if(count > 1 && i < count-2) { 0237 m_pqn += QLatin1String(" @or"); 0238 } 0239 } 0240 } 0241 break; 0242 case LCCN: 0243 { 0244 m_pqn.clear(); 0245 QString s = request().value(); 0246 s.remove(QLatin1Char('-')); 0247 QStringList lccnList = FieldFormat::splitValue(s); 0248 while(!lccnList.isEmpty()) { 0249 m_pqn += QLatin1String(" @or @attr 1=9 ") + lccnList.front(); 0250 if(lccnList.count() > 1) { 0251 m_pqn += QLatin1String(" @or"); 0252 } 0253 m_pqn += QLatin1String(" @attr 1=9 ") + LCCNValidator::formalize(lccnList.front()); 0254 lccnList.pop_front(); 0255 } 0256 } 0257 break; 0258 case Keyword: 0259 m_pqn = QLatin1String("@attr 1=1016 ") + svalue; 0260 break; 0261 case Raw: 0262 m_pqn = request().value(); 0263 break; 0264 default: 0265 myWarning() << "key not recognized: " << request().key(); 0266 stop(); 0267 return; 0268 } 0269 // m_pqn = QLatin1String("@attr 1=7 0253333490"); 0270 // myLog() << "PQN query = " << m_pqn; 0271 0272 if(m_conn) { 0273 m_conn->reset(); // reset counts 0274 } 0275 0276 process(); 0277 #else // HAVE_YAZ 0278 stop(); 0279 return; 0280 #endif 0281 } 0282 0283 void Z3950Fetcher::setCharacterSet(const QString& qcs_, const QString& rcs_) { 0284 m_queryCharSet = qcs_; 0285 m_responseCharSet = rcs_.isEmpty() ? qcs_ : rcs_; 0286 } 0287 0288 void Z3950Fetcher::continueSearch() { 0289 #ifdef HAVE_YAZ 0290 m_started = true; 0291 process(); 0292 #endif 0293 } 0294 0295 void Z3950Fetcher::stop() { 0296 if(!m_started) { 0297 return; 0298 } 0299 m_started = false; 0300 if(m_conn) { 0301 // give it a second to cleanup 0302 m_conn->abort(); 0303 m_conn->wait(1000); 0304 } 0305 emit signalDone(this); 0306 } 0307 0308 bool Z3950Fetcher::initMARC21Handler() { 0309 if(m_MARC21XMLHandler) { 0310 return true; 0311 } 0312 0313 QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("MARC21slim2MODS3.xsl")); 0314 if(xsltfile.isEmpty()) { 0315 myWarning() << "can not locate MARC21slim2MODS3.xsl."; 0316 return false; 0317 } 0318 0319 QUrl u = QUrl::fromLocalFile(xsltfile); 0320 0321 m_MARC21XMLHandler = new XSLTHandler(u); 0322 if(!m_MARC21XMLHandler->isValid()) { 0323 myWarning() << "error in MARC21slim2MODS3.xsl."; 0324 delete m_MARC21XMLHandler; 0325 m_MARC21XMLHandler = nullptr; 0326 return false; 0327 } 0328 return true; 0329 } 0330 0331 bool Z3950Fetcher::initUNIMARCHandler() { 0332 if(m_UNIMARCXMLHandler) { 0333 return true; 0334 } 0335 0336 QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("UNIMARC2MODS3.xsl")); 0337 if(xsltfile.isEmpty()) { 0338 myWarning() << "can not locate UNIMARC2MODS3.xsl."; 0339 return false; 0340 } 0341 0342 QUrl u = QUrl::fromLocalFile(xsltfile); 0343 0344 m_UNIMARCXMLHandler = new XSLTHandler(u); 0345 if(!m_UNIMARCXMLHandler->isValid()) { 0346 myWarning() << "error in UNIMARC2MODS3.xsl."; 0347 delete m_UNIMARCXMLHandler; 0348 m_UNIMARCXMLHandler = nullptr; 0349 return false; 0350 } 0351 return true; 0352 } 0353 0354 bool Z3950Fetcher::initMODSHandler() { 0355 if(m_MODSHandler) { 0356 return true; 0357 } 0358 0359 QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("mods2tellico.xsl")); 0360 if(xsltfile.isEmpty()) { 0361 myWarning() << "can not locate mods2tellico.xsl."; 0362 return false; 0363 } 0364 0365 QUrl u = QUrl::fromLocalFile(xsltfile); 0366 0367 m_MODSHandler = new XSLTHandler(u); 0368 if(!m_MODSHandler->isValid()) { 0369 myWarning() << "error in mods2tellico.xsl."; 0370 delete m_MODSHandler; 0371 m_MODSHandler = nullptr; 0372 // no use in keeping the MARC handlers now 0373 delete m_MARC21XMLHandler; 0374 m_MARC21XMLHandler = nullptr; 0375 delete m_UNIMARCXMLHandler; 0376 m_UNIMARCXMLHandler = nullptr; 0377 return false; 0378 } 0379 return true; 0380 } 0381 0382 void Z3950Fetcher::process() { 0383 if(m_conn) { 0384 m_conn->wait(); 0385 } else { 0386 m_conn = new Z3950Connection(this, m_host, m_port, m_dbname, m_syntax, m_esn); 0387 m_conn->setCharacterSet(m_queryCharSet, m_responseCharSet); 0388 if(!m_user.isEmpty()) { 0389 m_conn->setUserPassword(m_user, m_password); 0390 } 0391 } 0392 0393 m_conn->setQuery(m_pqn); 0394 m_conn->start(); 0395 } 0396 0397 void Z3950Fetcher::handleResult(const QString& result_) { 0398 if(result_.isEmpty()) { 0399 myDebug() << "empty record found, maybe the character encoding or record format is wrong?"; 0400 return; 0401 } 0402 // possible to get a race condition where the fetcher has been stopped 0403 // even as new results come in 0404 if(!m_started) { 0405 return; 0406 } 0407 0408 #if 0 0409 myWarning() << "Remove debug from z3950fetcher.cpp"; 0410 { 0411 QFile f1(QLatin1String("/tmp/z3950.txt")); 0412 if(f1.open(QIODevice::WriteOnly)) { 0413 // if(f1.open(QIODevice::WriteOnly | QIODevice::Append)) { 0414 QTextStream t(&f1); 0415 t.setCodec("UTF-8"); 0416 t << result_; 0417 } 0418 f1.close(); 0419 } 0420 #endif 0421 // assume always utf-8 0422 QString str, msg; 0423 Data::CollPtr coll; 0424 // not marc, has to be grs-1 0425 if(m_syntax == QLatin1String("grs-1")) { 0426 Import::GRS1Importer imp(result_); 0427 coll = imp.collection(); 0428 msg = imp.statusMessage(); 0429 } else if(m_syntax == QLatin1String("ads")) { 0430 Import::ADSImporter imp(result_); 0431 coll = imp.collection(); 0432 msg = imp.statusMessage(); 0433 } else { // now the MODS stuff 0434 if(m_syntax == QLatin1String("mods")) { 0435 str = result_; 0436 } else if(m_syntax == QLatin1String("unimarc") && initUNIMARCHandler()) { 0437 str = m_UNIMARCXMLHandler->applyStylesheet(result_); 0438 } else if(initMARC21Handler()) { // got to be usmarc/marc21 0439 str = m_MARC21XMLHandler->applyStylesheet(result_); 0440 } 0441 if(str.isEmpty() || !initMODSHandler()) { 0442 myDebug() << "empty string or can't init"; 0443 stop(); 0444 return; 0445 } 0446 #if 0 0447 myWarning() << "Remove debug from z3950fetcher.cpp"; 0448 { 0449 QFile f2(QLatin1String("/tmp/mods.xml")); 0450 // if(f2.open(QIODevice::WriteOnly)) { 0451 if(f2.open(QIODevice::WriteOnly | QIODevice::Append)) { 0452 QTextStream t(&f2); 0453 t.setCodec("UTF-8"); 0454 t << str; 0455 } 0456 f2.close(); 0457 } 0458 #endif 0459 Import::TellicoImporter imp(m_MODSHandler->applyStylesheet(str)); 0460 imp.setOptions(imp.options() ^ Import::ImportProgress); // no progress needed 0461 coll = imp.collection(); 0462 msg = imp.statusMessage(); 0463 } 0464 0465 if(!coll) { 0466 if(!msg.isEmpty()) { 0467 message(msg, MessageHandler::Warning); 0468 } 0469 myDebug() << "no collection pointer:" << msg; 0470 return; 0471 } 0472 0473 if(coll->entryCount() == 0) { 0474 // myDebug() << "no Tellico entry in result"; 0475 return; 0476 } 0477 0478 // since the Dewey and LoC field titles have a context in their i18n call here 0479 // but not in the mods2tellico.xsl stylesheet where the field is actually created 0480 // update the field titles here 0481 QHashIterator<QString, QString> i(allOptionalFields()); 0482 while(i.hasNext()) { 0483 i.next(); 0484 Data::FieldPtr field = coll->fieldByName(i.key()); 0485 if(field) { 0486 field->setTitle(i.value()); 0487 coll->modifyField(field); 0488 } 0489 } 0490 0491 Data::EntryList entries = coll->entries(); 0492 foreach(Data::EntryPtr entry, entries) { 0493 FetchResult* r = new FetchResult(this, entry); 0494 m_entries.insert(r->uid, entry); 0495 emit signalResultFound(r); 0496 } 0497 } 0498 0499 void Z3950Fetcher::done() { 0500 m_done = true; 0501 stop(); 0502 } 0503 0504 Tellico::Data::EntryPtr Z3950Fetcher::fetchEntryHook(uint uid_) { 0505 return m_entries[uid_]; 0506 } 0507 0508 void Z3950Fetcher::customEvent(QEvent* event_) { 0509 if(!m_conn) { 0510 return; 0511 } 0512 0513 if(event_->type() == Z3950ResultFound::uid()) { 0514 if(m_done) { 0515 myWarning() << "result returned after done signal!"; 0516 } 0517 Z3950ResultFound* e = static_cast<Z3950ResultFound*>(event_); 0518 handleResult(e->result()); 0519 } else if(event_->type() == Z3950ConnectionDone::uid()) { 0520 Z3950ConnectionDone* e = static_cast<Z3950ConnectionDone*>(event_); 0521 if(e->messageType() > -1) { 0522 message(e->message(), e->messageType()); 0523 } 0524 m_hasMoreResults = e->hasMoreResults(); 0525 m_conn->wait(); 0526 done(); 0527 } else if(event_->type() == Z3950SyntaxChange::uid()) { 0528 if(m_done) { 0529 myWarning() << "syntax changed after done signal!"; 0530 } 0531 Z3950SyntaxChange* e = static_cast<Z3950SyntaxChange*>(event_); 0532 if(m_syntax != e->syntax()) { 0533 m_syntax = e->syntax(); 0534 // it gets saved when saveConfigHook() get's called from the Fetcher() d'tor 0535 } 0536 } else { 0537 myWarning() << "weird type: " << event_->type(); 0538 } 0539 } 0540 0541 Tellico::Fetch::FetchRequest Z3950Fetcher::updateRequest(Data::EntryPtr entry_) { 0542 // myDebug() << source() << ": " << entry_->title(); 0543 QString isbn = entry_->field(QStringLiteral("isbn")); 0544 if(!isbn.isEmpty()) { 0545 return FetchRequest(Fetch::ISBN, isbn); 0546 } 0547 0548 QString lccn = entry_->field(QStringLiteral("lccn")); 0549 if(!lccn.isEmpty()) { 0550 return FetchRequest(Fetch::LCCN, lccn); 0551 } 0552 0553 // optimistically try searching for title and rely on Collection::sameEntry() to figure things out 0554 QString t = entry_->field(QStringLiteral("title")); 0555 if(!t.isEmpty()) { 0556 return FetchRequest(Fetch::Title, t); 0557 } 0558 return FetchRequest(); 0559 } 0560 0561 Tellico::Fetch::ConfigWidget* Z3950Fetcher::configWidget(QWidget* parent_) const { 0562 return new Z3950Fetcher::ConfigWidget(parent_, this); 0563 } 0564 0565 QString Z3950Fetcher::defaultName() { 0566 return i18n("z39.50 Server"); 0567 } 0568 0569 QString Z3950Fetcher::defaultIcon() { 0570 // return QLatin1String("network-server"); // rather arbitrary 0571 return QStringLiteral("network-server-database"); // rather arbitrary 0572 } 0573 0574 // static 0575 Tellico::StringHash Z3950Fetcher::allOptionalFields() { 0576 StringHash hash; 0577 hash[QStringLiteral("address")] = i18n("Address"); 0578 hash[QStringLiteral("abstract")] = i18n("Abstract"); 0579 hash[QStringLiteral("illustrator")] = i18n("Illustrator"); 0580 hash[QStringLiteral("dewey")] = i18nc("Dewey Decimal classification system", "Dewey Decimal"); 0581 hash[QStringLiteral("lcc")] = i18nc("Library of Congress classification system", "LoC Classification"); 0582 return hash; 0583 } 0584 0585 Z3950Fetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const Z3950Fetcher* fetcher_/*=0*/) 0586 : Fetch::ConfigWidget(parent_) { 0587 QGridLayout* l = new QGridLayout(optionsWidget()); 0588 l->setSpacing(4); 0589 l->setColumnStretch(1, 10); 0590 0591 int row = -1; 0592 0593 m_usePreset = new QCheckBox(i18n("Use preset &server:"), optionsWidget()); 0594 l->addWidget(m_usePreset, ++row, 0); 0595 connect(m_usePreset, &QAbstractButton::toggled, this, &ConfigWidget::slotTogglePreset); 0596 m_serverCombo = new GUI::ComboBox(optionsWidget()); 0597 void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated; 0598 connect(m_serverCombo, activatedInt, this, &ConfigWidget::slotPresetChanged); 0599 l->addWidget(m_serverCombo, row, 1); 0600 ++row; 0601 l->addWidget(new KSeparator(optionsWidget()), row, 0, 1, 2); 0602 l->setRowMinimumHeight(row, 10); 0603 0604 QLabel* label = new QLabel(i18n("Hos&t: "), optionsWidget()); 0605 l->addWidget(label, ++row, 0); 0606 m_hostEdit = new GUI::LineEdit(optionsWidget()); 0607 connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0608 connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::signalName); 0609 l->addWidget(m_hostEdit, row, 1); 0610 QString w = i18n("Enter the host name of the server."); 0611 label->setWhatsThis(w); 0612 m_hostEdit->setWhatsThis(w); 0613 label->setBuddy(m_hostEdit); 0614 0615 label = new QLabel(i18n("&Port: "), optionsWidget()); 0616 l->addWidget(label, ++row, 0); 0617 m_portSpinBox = new QSpinBox(optionsWidget()); 0618 m_portSpinBox->setMaximum(999999); 0619 m_portSpinBox->setMinimum(0); 0620 m_portSpinBox->setValue(Z3950_DEFAULT_PORT); 0621 void (QSpinBox::* valueChanged)(int) = &QSpinBox::valueChanged; 0622 connect(m_portSpinBox, valueChanged, this, &ConfigWidget::slotSetModified); 0623 l->addWidget(m_portSpinBox, row, 1); 0624 w = i18n("Enter the port number of the server. The default is %1.", Z3950_DEFAULT_PORT); 0625 label->setWhatsThis(w); 0626 m_portSpinBox->setWhatsThis(w); 0627 label->setBuddy(m_portSpinBox); 0628 0629 label = new QLabel(i18n("&Database: "), optionsWidget()); 0630 l->addWidget(label, ++row, 0); 0631 m_databaseEdit = new GUI::LineEdit(optionsWidget()); 0632 connect(m_databaseEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0633 l->addWidget(m_databaseEdit, row, 1); 0634 w = i18n("Enter the database name used by the server."); 0635 label->setWhatsThis(w); 0636 m_databaseEdit->setWhatsThis(w); 0637 label->setBuddy(m_databaseEdit); 0638 0639 label = new QLabel(i18n("Query character set: "), optionsWidget()); 0640 l->addWidget(label, ++row, 0); 0641 m_charSetCombo1 = new KComboBox(true, optionsWidget()); 0642 m_charSetCombo1->addItem(QString()); 0643 m_charSetCombo1->addItem(QStringLiteral("marc8")); 0644 m_charSetCombo1->addItem(QStringLiteral("iso-8859-1")); 0645 m_charSetCombo1->addItem(QStringLiteral("utf-8")); 0646 connect(m_charSetCombo1, &QComboBox::currentTextChanged, this, &ConfigWidget::slotSetModified); 0647 l->addWidget(m_charSetCombo1, row, 1); 0648 w = i18n("Enter the character set encoding used for queries by the z39.50 server. The most likely choice " 0649 "is MARC-8, although ISO-8859-1 is common as well."); 0650 label->setWhatsThis(w); 0651 m_charSetCombo1->setWhatsThis(w); 0652 label->setBuddy(m_charSetCombo1); 0653 0654 label = new QLabel(i18n("Results character set: "), optionsWidget()); 0655 l->addWidget(label, ++row, 0); 0656 m_charSetCombo2 = new KComboBox(true, optionsWidget()); 0657 m_charSetCombo2->addItem(QString()); 0658 m_charSetCombo2->addItem(QStringLiteral("marc8")); 0659 m_charSetCombo2->addItem(QStringLiteral("iso-8859-1")); 0660 m_charSetCombo2->addItem(QStringLiteral("utf-8")); 0661 connect(m_charSetCombo2, &QComboBox::currentTextChanged, this, &ConfigWidget::slotSetModified); 0662 l->addWidget(m_charSetCombo2, row, 1); 0663 w = i18n("Enter the character set encoding used for responses by the z39.50 server. The most likely choice " 0664 "is MARC-8, although ISO-8859-1 is common as well."); 0665 label->setWhatsThis(w); 0666 m_charSetCombo2->setWhatsThis(w); 0667 label->setBuddy(m_charSetCombo2); 0668 0669 label = new QLabel(i18n("&Format: "), optionsWidget()); 0670 l->addWidget(label, ++row, 0); 0671 m_syntaxCombo = new GUI::ComboBox(optionsWidget()); 0672 m_syntaxCombo->addItem(i18n("Auto-detect"), QString()); 0673 m_syntaxCombo->addItem(QStringLiteral("MODS"), QLatin1String("mods")); 0674 m_syntaxCombo->addItem(QStringLiteral("MARC21"), QLatin1String("marc21")); 0675 m_syntaxCombo->addItem(QStringLiteral("UNIMARC"), QLatin1String("unimarc")); 0676 m_syntaxCombo->addItem(QStringLiteral("USMARC"), QLatin1String("usmarc")); 0677 m_syntaxCombo->addItem(QStringLiteral("ADS"), QLatin1String("ads")); 0678 m_syntaxCombo->addItem(QStringLiteral("GRS-1"), QLatin1String("grs-1")); 0679 connect(m_syntaxCombo, &QComboBox::currentTextChanged, this, &ConfigWidget::slotSetModified); 0680 l->addWidget(m_syntaxCombo, row, 1); 0681 w = i18n("Enter the data format used by the z39.50 server. Tellico will attempt to " 0682 "automatically detect the best setting if <i>auto-detect</i> is selected."); 0683 label->setWhatsThis(w); 0684 m_syntaxCombo->setWhatsThis(w); 0685 label->setBuddy(m_syntaxCombo); 0686 0687 label = new QLabel(i18n("&User: "), optionsWidget()); 0688 l->addWidget(label, ++row, 0); 0689 m_userEdit = new GUI::LineEdit(optionsWidget()); 0690 m_userEdit->setPlaceholderText(i18n("Optional")); 0691 connect(m_userEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0692 l->addWidget(m_userEdit, row, 1); 0693 w = i18n("Enter the authentication user name used by the z39.50 database. Most servers " 0694 "do not need one."); 0695 label->setWhatsThis(w); 0696 m_userEdit->setWhatsThis(w); 0697 label->setBuddy(m_userEdit); 0698 0699 label = new QLabel(i18n("Pass&word: "), optionsWidget()); 0700 l->addWidget(label, ++row, 0); 0701 m_passwordEdit = new GUI::LineEdit(optionsWidget()); 0702 m_passwordEdit->setPlaceholderText(i18n("Optional")); 0703 m_passwordEdit->setEchoMode(QLineEdit::Password); 0704 connect(m_passwordEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0705 l->addWidget(m_passwordEdit, row, 1); 0706 w = i18n("Enter the authentication password used by the z39.50 database. Most servers " 0707 "do not need one. The password will be saved in plain text in the Tellico " 0708 "configuration file."); 0709 label->setWhatsThis(w); 0710 m_passwordEdit->setWhatsThis(w); 0711 label->setBuddy(m_passwordEdit); 0712 0713 l->setRowStretch(++row, 1); 0714 0715 // now add additional fields widget 0716 addFieldsWidget(Z3950Fetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0717 0718 loadPresets(fetcher_ ? fetcher_->m_preset : QString()); 0719 if(fetcher_) { 0720 m_hostEdit->setText(fetcher_->m_host); 0721 m_portSpinBox->setValue(fetcher_->m_port); 0722 m_databaseEdit->setText(fetcher_->m_dbname); 0723 m_userEdit->setText(fetcher_->m_user); 0724 m_passwordEdit->setText(fetcher_->m_password); 0725 m_charSetCombo1->setEditText(fetcher_->m_queryCharSet); 0726 m_charSetCombo2->setEditText(fetcher_->m_responseCharSet); 0727 // the syntax is detected automatically by the fetcher 0728 // since the config group gets deleted in the config file, 0729 // the value needs to be retained here 0730 m_syntax = fetcher_->m_syntax; 0731 m_syntaxCombo->setCurrentData(m_syntax); 0732 } 0733 KAcceleratorManager::manage(optionsWidget()); 0734 0735 // start with presets turned off 0736 m_usePreset->setChecked(fetcher_ && !fetcher_->m_preset.isEmpty()); 0737 0738 slotTogglePreset(m_usePreset->isChecked()); 0739 } 0740 0741 Z3950Fetcher::ConfigWidget::~ConfigWidget() { 0742 } 0743 0744 void Z3950Fetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) { 0745 if(m_usePreset->isChecked()) { 0746 QString presetID = m_serverCombo->currentData().toString(); 0747 config_.writeEntry("Preset", presetID); 0748 return; 0749 } 0750 config_.deleteEntry("Preset"); 0751 0752 QString s = m_hostEdit->text().trimmed(); 0753 if(!s.isEmpty()) { 0754 config_.writeEntry("Host", s); 0755 } 0756 int port = m_portSpinBox->value(); 0757 if(port > 0) { 0758 config_.writeEntry("Port", port); 0759 } 0760 s = m_databaseEdit->text().trimmed(); 0761 if(!s.isEmpty()) { 0762 config_.writeEntry("Database", s); 0763 } 0764 s = m_charSetCombo1->currentText().trimmed(); 0765 if(!s.isEmpty()) { 0766 config_.writeEntry("Charset", s); 0767 } 0768 s = m_charSetCombo2->currentText().trimmed(); 0769 if(!s.isEmpty()) { 0770 config_.writeEntry("CharsetResponse", s); 0771 } 0772 s = m_userEdit->text(); 0773 if(!s.isEmpty()) { 0774 config_.writeEntry("User", s); 0775 } 0776 s = m_passwordEdit->text(); 0777 if(!s.isEmpty()) { 0778 config_.writeEntry("Password", s); 0779 } 0780 s = m_syntaxCombo->currentData().toString(); 0781 if(!s.isEmpty()) { 0782 m_syntax = s; 0783 } 0784 config_.writeEntry("Syntax", m_syntax); 0785 } 0786 0787 void Z3950Fetcher::ConfigWidget::slotTogglePreset(bool on) { 0788 m_serverCombo->setEnabled(on); 0789 if(on) { 0790 emit signalName(m_serverCombo->currentText()); 0791 } 0792 m_hostEdit->setEnabled(!on); 0793 if(!on && !m_hostEdit->text().isEmpty()) { 0794 emit signalName(m_hostEdit->text()); 0795 } 0796 m_portSpinBox->setEnabled(!on); 0797 m_databaseEdit->setEnabled(!on); 0798 m_userEdit->setEnabled(!on); 0799 m_passwordEdit->setEnabled(!on); 0800 m_charSetCombo1->setEnabled(!on); 0801 m_charSetCombo2->setEnabled(!on); 0802 m_syntaxCombo->setEnabled(!on); 0803 if(on) { 0804 emit signalName(m_serverCombo->currentText()); 0805 } 0806 } 0807 0808 void Z3950Fetcher::ConfigWidget::slotPresetChanged() { 0809 emit signalName(m_serverCombo->currentText()); 0810 } 0811 0812 void Z3950Fetcher::ConfigWidget::loadPresets(const QString& current_) { 0813 const QString lang = QLocale().uiLanguages().constFirst(); 0814 const QString lang2A = lang.contains(QLatin1Char('-')) ? lang.section(QLatin1Char('-'), 0, 0) : lang; 0815 0816 QString serverFile = DataFileRegistry::self()->locate(QStringLiteral("z3950-servers.cfg")); 0817 if(serverFile.isEmpty()) { 0818 myWarning() << "no z3950 servers file found"; 0819 return; 0820 } 0821 0822 int idx = -1; 0823 0824 KConfig serverConfig(serverFile, KConfig::SimpleConfig); 0825 const QStringList servers = serverConfig.groupList(); 0826 // I want the list of servers sorted by name so use QMap instead of QHash 0827 QMap<QString, QString> serverNameMap; 0828 QHash<QString, QIcon> flags; 0829 for(QStringList::ConstIterator server = servers.constBegin(); server != servers.constEnd(); ++server) { 0830 if((*server).isEmpty()) { 0831 myDebug() << "empty id"; 0832 continue; 0833 } 0834 KConfigGroup cfg(&serverConfig, *server); 0835 const QString name = cfg.readEntry("Name"); 0836 if(!name.isEmpty()) { 0837 serverNameMap.insert(name, *server); 0838 } 0839 } 0840 for(QMap<QString, QString>::ConstIterator it = serverNameMap.constBegin(); it != serverNameMap.constEnd(); ++it) { 0841 const QString name = it.key(); 0842 const QString group = it.value(); 0843 KConfigGroup cfg(&serverConfig, group); 0844 const QString country = cfg.readEntry("Country", QString()); 0845 0846 QIcon icon; 0847 if(!country.isEmpty()) { 0848 QHash<QString, QIcon>::ConstIterator it = flags.constFind(country); 0849 if(it != flags.constEnd()) { 0850 icon = it.value(); 0851 } else { 0852 const QString flag = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0853 QStringLiteral("kf5/locale/countries/%1/flag.png").arg(country)); 0854 if (!flag.isEmpty()) { 0855 icon = QIcon(flag); 0856 flags.insert(country, icon); 0857 } 0858 } 0859 } 0860 m_serverCombo->addItem(icon, name, group); 0861 0862 if(current_.isEmpty() && idx == -1) { 0863 // set the initial selection to something depending on the language 0864 const QStringList locales = cfg.readEntry("Locale", QStringList()); 0865 if(locales.contains(lang) || locales.contains(lang2A)) { 0866 idx = m_serverCombo->count() - 1; 0867 } 0868 } else if(group == current_) { 0869 idx = m_serverCombo->count() - 1; 0870 } 0871 } 0872 if(idx > -1) { 0873 m_serverCombo->setCurrentIndex(idx); 0874 } 0875 } 0876 0877 QString Z3950Fetcher::ConfigWidget::preferredName() const { 0878 if(m_usePreset->isChecked()) { 0879 return m_serverCombo->currentText(); 0880 } 0881 QString s = m_hostEdit->text(); 0882 return s.isEmpty() ? i18n("z39.50 Server") : s; 0883 }