File indexing completed on 2024-05-12 05:09:46
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() << source() << "- host and dbname are not set!"; 0195 stop(); 0196 return; 0197 } 0198 0199 QString svalue = request().value(); 0200 bool hasQuotes = svalue.startsWith(QLatin1Char('"')) && svalue.endsWith(QLatin1Char('"')); 0201 if(!hasQuotes) { 0202 hasQuotes = svalue.startsWith(QLatin1Char('\'')) && svalue.endsWith(QLatin1Char('\'')); 0203 } 0204 if(!hasQuotes) { 0205 svalue = QLatin1Char('"') + svalue + QLatin1Char('"'); 0206 } 0207 0208 switch(request().key()) { 0209 case Title: 0210 m_pqn = QLatin1String("@attr 1=4 ") + svalue; 0211 break; 0212 case Person: 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() << source() << "- key not recognized:" << request().key(); 0266 stop(); 0267 return; 0268 } 0269 // myLog() << "PQN query = " << m_pqn; 0270 0271 if(m_conn) { 0272 m_conn->reset(); // reset counts 0273 } 0274 0275 process(); 0276 #else // HAVE_YAZ 0277 stop(); 0278 return; 0279 #endif 0280 } 0281 0282 void Z3950Fetcher::setCharacterSet(const QString& qcs_, const QString& rcs_) { 0283 m_queryCharSet = qcs_; 0284 m_responseCharSet = rcs_.isEmpty() ? qcs_ : rcs_; 0285 } 0286 0287 void Z3950Fetcher::continueSearch() { 0288 #ifdef HAVE_YAZ 0289 m_started = true; 0290 process(); 0291 #endif 0292 } 0293 0294 void Z3950Fetcher::stop() { 0295 if(!m_started) { 0296 return; 0297 } 0298 m_started = false; 0299 if(m_conn) { 0300 // give it a second to cleanup 0301 m_conn->abort(); 0302 m_conn->wait(1000); 0303 } 0304 emit signalDone(this); 0305 } 0306 0307 bool Z3950Fetcher::initMARC21Handler() { 0308 if(m_MARC21XMLHandler) { 0309 return true; 0310 } 0311 0312 QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("MARC21slim2MODS3.xsl")); 0313 if(xsltfile.isEmpty()) { 0314 myWarning() << "can not locate MARC21slim2MODS3.xsl."; 0315 return false; 0316 } 0317 0318 QUrl u = QUrl::fromLocalFile(xsltfile); 0319 0320 m_MARC21XMLHandler = new XSLTHandler(u); 0321 if(!m_MARC21XMLHandler->isValid()) { 0322 myWarning() << "error in MARC21slim2MODS3.xsl."; 0323 delete m_MARC21XMLHandler; 0324 m_MARC21XMLHandler = nullptr; 0325 return false; 0326 } 0327 return true; 0328 } 0329 0330 bool Z3950Fetcher::initUNIMARCHandler() { 0331 if(m_UNIMARCXMLHandler) { 0332 return true; 0333 } 0334 0335 QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("UNIMARC2MODS3.xsl")); 0336 if(xsltfile.isEmpty()) { 0337 myWarning() << "can not locate UNIMARC2MODS3.xsl."; 0338 return false; 0339 } 0340 0341 QUrl u = QUrl::fromLocalFile(xsltfile); 0342 0343 m_UNIMARCXMLHandler = new XSLTHandler(u); 0344 if(!m_UNIMARCXMLHandler->isValid()) { 0345 myWarning() << "error in UNIMARC2MODS3.xsl."; 0346 delete m_UNIMARCXMLHandler; 0347 m_UNIMARCXMLHandler = nullptr; 0348 return false; 0349 } 0350 return true; 0351 } 0352 0353 bool Z3950Fetcher::initMODSHandler() { 0354 if(m_MODSHandler) { 0355 return true; 0356 } 0357 0358 QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("mods2tellico.xsl")); 0359 if(xsltfile.isEmpty()) { 0360 myWarning() << "can not locate mods2tellico.xsl."; 0361 return false; 0362 } 0363 0364 QUrl u = QUrl::fromLocalFile(xsltfile); 0365 0366 m_MODSHandler = new XSLTHandler(u); 0367 if(!m_MODSHandler->isValid()) { 0368 myWarning() << "error in mods2tellico.xsl."; 0369 delete m_MODSHandler; 0370 m_MODSHandler = nullptr; 0371 // no use in keeping the MARC handlers now 0372 delete m_MARC21XMLHandler; 0373 m_MARC21XMLHandler = nullptr; 0374 delete m_UNIMARCXMLHandler; 0375 m_UNIMARCXMLHandler = nullptr; 0376 return false; 0377 } 0378 return true; 0379 } 0380 0381 void Z3950Fetcher::process() { 0382 if(m_conn) { 0383 m_conn->wait(); 0384 } else { 0385 m_conn = new Z3950Connection(this, m_host, m_port, m_dbname, m_syntax, m_esn); 0386 m_conn->setCharacterSet(m_queryCharSet, m_responseCharSet); 0387 if(!m_user.isEmpty()) { 0388 m_conn->setUserPassword(m_user, m_password); 0389 } 0390 } 0391 0392 m_conn->setQuery(m_pqn); 0393 m_conn->start(); 0394 } 0395 0396 void Z3950Fetcher::handleResult(const QString& result_) { 0397 if(result_.isEmpty()) { 0398 myDebug() << "empty record found, maybe the character encoding or record format is wrong?"; 0399 return; 0400 } 0401 // possible to get a race condition where the fetcher has been stopped 0402 // even as new results come in 0403 if(!m_started) { 0404 return; 0405 } 0406 0407 #if 0 0408 myWarning() << "Remove debug from z3950fetcher.cpp"; 0409 { 0410 QFile f1(QLatin1String("/tmp/z3950.txt")); 0411 if(f1.open(QIODevice::WriteOnly)) { 0412 // if(f1.open(QIODevice::WriteOnly | QIODevice::Append)) { 0413 QTextStream t(&f1); 0414 t.setCodec("UTF-8"); 0415 t << result_; 0416 } 0417 f1.close(); 0418 } 0419 #endif 0420 // assume always utf-8 0421 QString str, msg; 0422 Data::CollPtr coll; 0423 // not marc, has to be grs-1 0424 if(m_syntax == QLatin1String("grs-1")) { 0425 Import::GRS1Importer imp(result_); 0426 coll = imp.collection(); 0427 msg = imp.statusMessage(); 0428 } else if(m_syntax == QLatin1String("ads")) { 0429 Import::ADSImporter imp(result_); 0430 coll = imp.collection(); 0431 msg = imp.statusMessage(); 0432 } else { // now the MODS stuff 0433 if(m_syntax == QLatin1String("mods")) { 0434 str = result_; 0435 } else if(m_syntax == QLatin1String("unimarc") && initUNIMARCHandler()) { 0436 str = m_UNIMARCXMLHandler->applyStylesheet(result_); 0437 } else if(initMARC21Handler()) { // got to be usmarc/marc21 0438 str = m_MARC21XMLHandler->applyStylesheet(result_); 0439 } 0440 if(str.isEmpty() || !initMODSHandler()) { 0441 myDebug() << "empty string or can't init"; 0442 stop(); 0443 return; 0444 } 0445 #if 0 0446 myWarning() << "Remove debug from z3950fetcher.cpp"; 0447 { 0448 QFile f2(QLatin1String("/tmp/mods.xml")); 0449 // if(f2.open(QIODevice::WriteOnly)) { 0450 if(f2.open(QIODevice::WriteOnly | QIODevice::Append)) { 0451 QTextStream t(&f2); 0452 t.setCodec("UTF-8"); 0453 t << str; 0454 } 0455 f2.close(); 0456 } 0457 #endif 0458 Import::TellicoImporter imp(m_MODSHandler->applyStylesheet(str)); 0459 imp.setOptions(imp.options() ^ Import::ImportProgress); // no progress needed 0460 coll = imp.collection(); 0461 msg = imp.statusMessage(); 0462 } 0463 0464 if(!coll) { 0465 if(!msg.isEmpty()) { 0466 message(msg, MessageHandler::Warning); 0467 } 0468 myDebug() << "no collection pointer:" << msg; 0469 return; 0470 } 0471 0472 if(coll->entryCount() == 0) { 0473 // myDebug() << "no Tellico entry in result"; 0474 return; 0475 } 0476 0477 // since the Dewey and LoC field titles have a context in their i18n call here 0478 // but not in the mods2tellico.xsl stylesheet where the field is actually created 0479 // update the field titles here 0480 QHashIterator<QString, QString> i(allOptionalFields()); 0481 while(i.hasNext()) { 0482 i.next(); 0483 Data::FieldPtr field = coll->fieldByName(i.key()); 0484 if(field) { 0485 field->setTitle(i.value()); 0486 coll->modifyField(field); 0487 } 0488 } 0489 0490 Data::EntryList entries = coll->entries(); 0491 foreach(Data::EntryPtr entry, entries) { 0492 FetchResult* r = new FetchResult(this, entry); 0493 m_entries.insert(r->uid, entry); 0494 emit signalResultFound(r); 0495 } 0496 } 0497 0498 void Z3950Fetcher::done() { 0499 m_done = true; 0500 stop(); 0501 } 0502 0503 Tellico::Data::EntryPtr Z3950Fetcher::fetchEntryHook(uint uid_) { 0504 return m_entries[uid_]; 0505 } 0506 0507 void Z3950Fetcher::customEvent(QEvent* event_) { 0508 if(!m_conn) { 0509 return; 0510 } 0511 0512 if(event_->type() == Z3950ResultFound::uid()) { 0513 if(m_done) { 0514 myWarning() << "result returned after done signal!"; 0515 } 0516 Z3950ResultFound* e = static_cast<Z3950ResultFound*>(event_); 0517 handleResult(e->result()); 0518 } else if(event_->type() == Z3950ConnectionDone::uid()) { 0519 Z3950ConnectionDone* e = static_cast<Z3950ConnectionDone*>(event_); 0520 if(e->messageType() > -1) { 0521 message(e->message(), e->messageType()); 0522 } 0523 m_hasMoreResults = e->hasMoreResults(); 0524 m_conn->wait(); 0525 done(); 0526 } else if(event_->type() == Z3950SyntaxChange::uid()) { 0527 if(m_done) { 0528 myWarning() << "syntax changed after done signal!"; 0529 } 0530 Z3950SyntaxChange* e = static_cast<Z3950SyntaxChange*>(event_); 0531 if(m_syntax != e->syntax()) { 0532 m_syntax = e->syntax(); 0533 // it gets saved when saveConfigHook() get's called from the Fetcher() d'tor 0534 } 0535 } else { 0536 myWarning() << "weird type: " << event_->type(); 0537 } 0538 } 0539 0540 Tellico::Fetch::FetchRequest Z3950Fetcher::updateRequest(Data::EntryPtr entry_) { 0541 // myDebug() << source() << ": " << entry_->title(); 0542 QString isbn = entry_->field(QStringLiteral("isbn")); 0543 if(!isbn.isEmpty()) { 0544 return FetchRequest(Fetch::ISBN, isbn); 0545 } 0546 0547 QString lccn = entry_->field(QStringLiteral("lccn")); 0548 if(!lccn.isEmpty()) { 0549 return FetchRequest(Fetch::LCCN, lccn); 0550 } 0551 0552 // optimistically try searching for title and rely on Collection::sameEntry() to figure things out 0553 QString t = entry_->field(QStringLiteral("title")); 0554 if(!t.isEmpty()) { 0555 return FetchRequest(Fetch::Title, t); 0556 } 0557 return FetchRequest(); 0558 } 0559 0560 Tellico::Fetch::ConfigWidget* Z3950Fetcher::configWidget(QWidget* parent_) const { 0561 return new Z3950Fetcher::ConfigWidget(parent_, this); 0562 } 0563 0564 QString Z3950Fetcher::defaultName() { 0565 return i18n("z39.50 Server"); 0566 } 0567 0568 QString Z3950Fetcher::defaultIcon() { 0569 return QStringLiteral("network-server-database"); // rather arbitrary 0570 } 0571 0572 // static 0573 Tellico::StringHash Z3950Fetcher::allOptionalFields() { 0574 StringHash hash; 0575 hash[QStringLiteral("address")] = i18n("Address"); 0576 hash[QStringLiteral("abstract")] = i18n("Abstract"); 0577 hash[QStringLiteral("illustrator")] = i18n("Illustrator"); 0578 hash[QStringLiteral("dewey")] = i18nc("Dewey Decimal classification system", "Dewey Decimal"); 0579 hash[QStringLiteral("lcc")] = i18nc("Library of Congress classification system", "LoC Classification"); 0580 return hash; 0581 } 0582 0583 Z3950Fetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const Z3950Fetcher* fetcher_/*=0*/) 0584 : Fetch::ConfigWidget(parent_) { 0585 QGridLayout* l = new QGridLayout(optionsWidget()); 0586 l->setSpacing(4); 0587 l->setColumnStretch(1, 10); 0588 0589 int row = -1; 0590 0591 m_usePreset = new QCheckBox(i18n("Use preset &server:"), optionsWidget()); 0592 l->addWidget(m_usePreset, ++row, 0); 0593 connect(m_usePreset, &QAbstractButton::toggled, this, &ConfigWidget::slotTogglePreset); 0594 m_serverCombo = new GUI::ComboBox(optionsWidget()); 0595 void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated; 0596 connect(m_serverCombo, activatedInt, this, &ConfigWidget::slotPresetChanged); 0597 l->addWidget(m_serverCombo, row, 1); 0598 ++row; 0599 l->addWidget(new KSeparator(optionsWidget()), row, 0, 1, 2); 0600 l->setRowMinimumHeight(row, 10); 0601 0602 QLabel* label = new QLabel(i18n("Hos&t: "), optionsWidget()); 0603 l->addWidget(label, ++row, 0); 0604 m_hostEdit = new GUI::LineEdit(optionsWidget()); 0605 connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0606 connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::signalName); 0607 l->addWidget(m_hostEdit, row, 1); 0608 QString w = i18n("Enter the host name of the server."); 0609 label->setWhatsThis(w); 0610 m_hostEdit->setWhatsThis(w); 0611 label->setBuddy(m_hostEdit); 0612 0613 label = new QLabel(i18n("&Port: "), optionsWidget()); 0614 l->addWidget(label, ++row, 0); 0615 m_portSpinBox = new QSpinBox(optionsWidget()); 0616 m_portSpinBox->setMaximum(999999); 0617 m_portSpinBox->setMinimum(0); 0618 m_portSpinBox->setValue(Z3950_DEFAULT_PORT); 0619 void (QSpinBox::* valueChanged)(int) = &QSpinBox::valueChanged; 0620 connect(m_portSpinBox, valueChanged, this, &ConfigWidget::slotSetModified); 0621 l->addWidget(m_portSpinBox, row, 1); 0622 w = i18n("Enter the port number of the server. The default is %1.", Z3950_DEFAULT_PORT); 0623 label->setWhatsThis(w); 0624 m_portSpinBox->setWhatsThis(w); 0625 label->setBuddy(m_portSpinBox); 0626 0627 label = new QLabel(i18n("&Database: "), optionsWidget()); 0628 l->addWidget(label, ++row, 0); 0629 m_databaseEdit = new GUI::LineEdit(optionsWidget()); 0630 connect(m_databaseEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0631 l->addWidget(m_databaseEdit, row, 1); 0632 w = i18n("Enter the database name used by the server."); 0633 label->setWhatsThis(w); 0634 m_databaseEdit->setWhatsThis(w); 0635 label->setBuddy(m_databaseEdit); 0636 0637 label = new QLabel(i18n("Query character set: "), optionsWidget()); 0638 l->addWidget(label, ++row, 0); 0639 m_charSetCombo1 = new KComboBox(true, optionsWidget()); 0640 m_charSetCombo1->addItem(QString()); 0641 m_charSetCombo1->addItem(QStringLiteral("marc8")); 0642 m_charSetCombo1->addItem(QStringLiteral("iso-8859-1")); 0643 m_charSetCombo1->addItem(QStringLiteral("utf-8")); 0644 connect(m_charSetCombo1, &QComboBox::currentTextChanged, this, &ConfigWidget::slotSetModified); 0645 l->addWidget(m_charSetCombo1, row, 1); 0646 w = i18n("Enter the character set encoding used for queries by the z39.50 server. The most likely choice " 0647 "is MARC-8, although ISO-8859-1 is common as well."); 0648 label->setWhatsThis(w); 0649 m_charSetCombo1->setWhatsThis(w); 0650 label->setBuddy(m_charSetCombo1); 0651 0652 label = new QLabel(i18n("Results character set: "), optionsWidget()); 0653 l->addWidget(label, ++row, 0); 0654 m_charSetCombo2 = new KComboBox(true, optionsWidget()); 0655 m_charSetCombo2->addItem(QString()); 0656 m_charSetCombo2->addItem(QStringLiteral("marc8")); 0657 m_charSetCombo2->addItem(QStringLiteral("iso-8859-1")); 0658 m_charSetCombo2->addItem(QStringLiteral("utf-8")); 0659 connect(m_charSetCombo2, &QComboBox::currentTextChanged, this, &ConfigWidget::slotSetModified); 0660 l->addWidget(m_charSetCombo2, row, 1); 0661 w = i18n("Enter the character set encoding used for responses by the z39.50 server. The most likely choice " 0662 "is MARC-8, although ISO-8859-1 is common as well."); 0663 label->setWhatsThis(w); 0664 m_charSetCombo2->setWhatsThis(w); 0665 label->setBuddy(m_charSetCombo2); 0666 0667 label = new QLabel(i18n("&Format: "), optionsWidget()); 0668 l->addWidget(label, ++row, 0); 0669 m_syntaxCombo = new GUI::ComboBox(optionsWidget()); 0670 m_syntaxCombo->addItem(i18n("Auto-detect"), QString()); 0671 m_syntaxCombo->addItem(QStringLiteral("MODS"), QLatin1String("mods")); 0672 m_syntaxCombo->addItem(QStringLiteral("MARC21"), QLatin1String("marc21")); 0673 m_syntaxCombo->addItem(QStringLiteral("UNIMARC"), QLatin1String("unimarc")); 0674 m_syntaxCombo->addItem(QStringLiteral("USMARC"), QLatin1String("usmarc")); 0675 m_syntaxCombo->addItem(QStringLiteral("ADS"), QLatin1String("ads")); 0676 m_syntaxCombo->addItem(QStringLiteral("GRS-1"), QLatin1String("grs-1")); 0677 connect(m_syntaxCombo, &QComboBox::currentTextChanged, this, &ConfigWidget::slotSetModified); 0678 l->addWidget(m_syntaxCombo, row, 1); 0679 w = i18n("Enter the data format used by the z39.50 server. Tellico will attempt to " 0680 "automatically detect the best setting if <i>auto-detect</i> is selected."); 0681 label->setWhatsThis(w); 0682 m_syntaxCombo->setWhatsThis(w); 0683 label->setBuddy(m_syntaxCombo); 0684 0685 label = new QLabel(i18n("&User: "), optionsWidget()); 0686 l->addWidget(label, ++row, 0); 0687 m_userEdit = new GUI::LineEdit(optionsWidget()); 0688 m_userEdit->setPlaceholderText(i18n("Optional")); 0689 connect(m_userEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0690 l->addWidget(m_userEdit, row, 1); 0691 w = i18n("Enter the authentication user name used by the z39.50 database. Most servers " 0692 "do not need one."); 0693 label->setWhatsThis(w); 0694 m_userEdit->setWhatsThis(w); 0695 label->setBuddy(m_userEdit); 0696 0697 label = new QLabel(i18n("Pass&word: "), optionsWidget()); 0698 l->addWidget(label, ++row, 0); 0699 m_passwordEdit = new GUI::LineEdit(optionsWidget()); 0700 m_passwordEdit->setPlaceholderText(i18n("Optional")); 0701 m_passwordEdit->setEchoMode(QLineEdit::Password); 0702 connect(m_passwordEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0703 l->addWidget(m_passwordEdit, row, 1); 0704 w = i18n("Enter the authentication password used by the z39.50 database. Most servers " 0705 "do not need one. The password will be saved in plain text in the Tellico " 0706 "configuration file."); 0707 label->setWhatsThis(w); 0708 m_passwordEdit->setWhatsThis(w); 0709 label->setBuddy(m_passwordEdit); 0710 0711 l->setRowStretch(++row, 1); 0712 0713 // now add additional fields widget 0714 addFieldsWidget(Z3950Fetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0715 0716 loadPresets(fetcher_ ? fetcher_->m_preset : QString()); 0717 if(fetcher_) { 0718 m_hostEdit->setText(fetcher_->m_host); 0719 m_portSpinBox->setValue(fetcher_->m_port); 0720 m_databaseEdit->setText(fetcher_->m_dbname); 0721 m_userEdit->setText(fetcher_->m_user); 0722 m_passwordEdit->setText(fetcher_->m_password); 0723 m_charSetCombo1->setEditText(fetcher_->m_queryCharSet); 0724 m_charSetCombo2->setEditText(fetcher_->m_responseCharSet); 0725 // the syntax is detected automatically by the fetcher 0726 // since the config group gets deleted in the config file, 0727 // the value needs to be retained here 0728 m_syntax = fetcher_->m_syntax; 0729 m_syntaxCombo->setCurrentData(m_syntax); 0730 } 0731 KAcceleratorManager::manage(optionsWidget()); 0732 0733 // start with presets turned off 0734 m_usePreset->setChecked(fetcher_ && !fetcher_->m_preset.isEmpty()); 0735 0736 slotTogglePreset(m_usePreset->isChecked()); 0737 } 0738 0739 Z3950Fetcher::ConfigWidget::~ConfigWidget() { 0740 } 0741 0742 void Z3950Fetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) { 0743 if(m_usePreset->isChecked()) { 0744 QString presetID = m_serverCombo->currentData().toString(); 0745 config_.writeEntry("Preset", presetID); 0746 return; 0747 } 0748 config_.deleteEntry("Preset"); 0749 0750 QString s = m_hostEdit->text().trimmed(); 0751 if(!s.isEmpty()) { 0752 config_.writeEntry("Host", s); 0753 } 0754 int port = m_portSpinBox->value(); 0755 if(port > 0) { 0756 config_.writeEntry("Port", port); 0757 } 0758 s = m_databaseEdit->text().trimmed(); 0759 if(!s.isEmpty()) { 0760 config_.writeEntry("Database", s); 0761 } 0762 s = m_charSetCombo1->currentText().trimmed(); 0763 if(!s.isEmpty()) { 0764 config_.writeEntry("Charset", s); 0765 } 0766 s = m_charSetCombo2->currentText().trimmed(); 0767 if(!s.isEmpty()) { 0768 config_.writeEntry("CharsetResponse", s); 0769 } 0770 s = m_userEdit->text(); 0771 if(!s.isEmpty()) { 0772 config_.writeEntry("User", s); 0773 } 0774 s = m_passwordEdit->text(); 0775 if(!s.isEmpty()) { 0776 config_.writeEntry("Password", s); 0777 } 0778 s = m_syntaxCombo->currentData().toString(); 0779 if(!s.isEmpty()) { 0780 m_syntax = s; 0781 } 0782 config_.writeEntry("Syntax", m_syntax); 0783 } 0784 0785 void Z3950Fetcher::ConfigWidget::slotTogglePreset(bool on) { 0786 m_serverCombo->setEnabled(on); 0787 if(on) { 0788 emit signalName(m_serverCombo->currentText()); 0789 } 0790 m_hostEdit->setEnabled(!on); 0791 if(!on && !m_hostEdit->text().isEmpty()) { 0792 emit signalName(m_hostEdit->text()); 0793 } 0794 m_portSpinBox->setEnabled(!on); 0795 m_databaseEdit->setEnabled(!on); 0796 m_userEdit->setEnabled(!on); 0797 m_passwordEdit->setEnabled(!on); 0798 m_charSetCombo1->setEnabled(!on); 0799 m_charSetCombo2->setEnabled(!on); 0800 m_syntaxCombo->setEnabled(!on); 0801 if(on) { 0802 emit signalName(m_serverCombo->currentText()); 0803 } 0804 } 0805 0806 void Z3950Fetcher::ConfigWidget::slotPresetChanged() { 0807 emit signalName(m_serverCombo->currentText()); 0808 } 0809 0810 void Z3950Fetcher::ConfigWidget::loadPresets(const QString& current_) { 0811 const QString lang = QLocale().uiLanguages().constFirst(); 0812 const QString lang2A = lang.contains(QLatin1Char('-')) ? lang.section(QLatin1Char('-'), 0, 0) : lang; 0813 0814 QString serverFile = DataFileRegistry::self()->locate(QStringLiteral("z3950-servers.cfg")); 0815 if(serverFile.isEmpty()) { 0816 myWarning() << "no z3950 servers file found"; 0817 return; 0818 } 0819 0820 int idx = -1; 0821 0822 KConfig serverConfig(serverFile, KConfig::SimpleConfig); 0823 const QStringList servers = serverConfig.groupList(); 0824 // I want the list of servers sorted by name so use QMap instead of QHash 0825 QMap<QString, QString> serverNameMap; 0826 QHash<QString, QIcon> flags; 0827 for(QStringList::ConstIterator server = servers.constBegin(); server != servers.constEnd(); ++server) { 0828 if((*server).isEmpty()) { 0829 myDebug() << "empty id"; 0830 continue; 0831 } 0832 KConfigGroup cfg(&serverConfig, *server); 0833 const QString name = cfg.readEntry("Name"); 0834 if(!name.isEmpty()) { 0835 serverNameMap.insert(name, *server); 0836 } 0837 } 0838 for(QMap<QString, QString>::ConstIterator it = serverNameMap.constBegin(); it != serverNameMap.constEnd(); ++it) { 0839 const QString name = it.key(); 0840 const QString group = it.value(); 0841 KConfigGroup cfg(&serverConfig, group); 0842 const QString country = cfg.readEntry("Country", QString()); 0843 0844 QIcon icon; 0845 if(!country.isEmpty()) { 0846 QHash<QString, QIcon>::ConstIterator it = flags.constFind(country); 0847 if(it != flags.constEnd()) { 0848 icon = it.value(); 0849 } else { 0850 const QString flag = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0851 QStringLiteral("kf5/locale/countries/%1/flag.png").arg(country)); 0852 if (!flag.isEmpty()) { 0853 icon = QIcon(flag); 0854 flags.insert(country, icon); 0855 } 0856 } 0857 } 0858 m_serverCombo->addItem(icon, name, group); 0859 0860 if(current_.isEmpty() && idx == -1) { 0861 // set the initial selection to something depending on the language 0862 const QStringList locales = cfg.readEntry("Locale", QStringList()); 0863 if(locales.contains(lang) || locales.contains(lang2A)) { 0864 idx = m_serverCombo->count() - 1; 0865 } 0866 } else if(group == current_) { 0867 idx = m_serverCombo->count() - 1; 0868 } 0869 } 0870 if(idx > -1) { 0871 m_serverCombo->setCurrentIndex(idx); 0872 } 0873 } 0874 0875 QString Z3950Fetcher::ConfigWidget::preferredName() const { 0876 if(m_usePreset->isChecked()) { 0877 return m_serverCombo->currentText(); 0878 } 0879 QString s = m_hostEdit->text(); 0880 return s.isEmpty() ? i18n("z39.50 Server") : s; 0881 }