1 /***************************************************************************
2     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
3  ***************************************************************************/
4 
5 /***************************************************************************
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or         *
8  *   modify it under the terms of the GNU General Public License as        *
9  *   published by the Free Software Foundation; either version 2 of        *
10  *   the License or (at your option) version 3 or any later version        *
11  *   accepted by the membership of KDE e.V. (or its successor approved     *
12  *   by the membership of KDE e.V.), which shall act as a proxy            *
13  *   defined in Section 14 of version 3 of the license.                    *
14  *                                                                         *
15  *   This program is distributed in the hope that it will be useful,       *
16  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
18  *   GNU General Public License for more details.                          *
19  *                                                                         *
20  *   You should have received a copy of the GNU General Public License     *
21  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
22  *                                                                         *
23  ***************************************************************************/
24 
25 #include "srufetcher.h"
26 #include "../fieldformat.h"
27 #include "../collection.h"
28 #include "../translators/tellico_xml.h"
29 #include "../translators/xslthandler.h"
30 #include "../translators/tellicoimporter.h"
31 #include "../utils/guiproxy.h"
32 #include "../gui/lineedit.h"
33 #include "../gui/combobox.h"
34 #include "../gui/stringmapwidget.h"
35 #include "../utils/string_utils.h"
36 #include "../utils/lccnvalidator.h"
37 #include "../utils/isbnvalidator.h"
38 #include "../utils/datafileregistry.h"
39 #include "../tellico_debug.h"
40 
41 #include <KLocalizedString>
42 #include <KIO/Job>
43 #include <KJobUiDelegate>
44 #include <KJobWidgets/KJobWidgets>
45 #include <KConfigGroup>
46 #include <KComboBox>
47 #include <KAcceleratorManager>
48 
49 #include <QSpinBox>
50 #include <QLabel>
51 #include <QGridLayout>
52 #include <QFile>
53 #include <QUrlQuery>
54 #include <QDomDocument>
55 
56 namespace {
57   // 7090 was the old default port, but that was just because LoC used it
58   // let's use default HTTP port of 80 now
59   static const int SRU_DEFAULT_PORT = 80;
60   static const int SRU_MAX_RECORDS = 25;
61 }
62 
63 using namespace Tellico;
64 using Tellico::Fetch::SRUFetcher;
65 
SRUFetcher(QObject * parent_)66 SRUFetcher::SRUFetcher(QObject* parent_)
67     : Fetcher(parent_), m_port(SRU_DEFAULT_PORT), m_job(nullptr), m_MARCXMLHandler(nullptr), m_MODSHandler(nullptr), m_SRWHandler(nullptr), m_started(false) {
68 }
69 
SRUFetcher(const QString & name_,const QString & host_,uint port_,const QString & path_,const QString & format_,QObject * parent_)70 SRUFetcher::SRUFetcher(const QString& name_, const QString& host_, uint port_, const QString& path_,
71                        const QString& format_, QObject* parent_) : Fetcher(parent_),
72       m_host(host_), m_port(port_), m_path(path_), m_format(format_),
73       m_job(nullptr), m_MARCXMLHandler(nullptr), m_MODSHandler(nullptr), m_SRWHandler(nullptr), m_started(false) {
74   m_name = name_; // m_name is protected in super class
75   if(!m_path.startsWith(QLatin1Char('/'))) {
76     m_path.prepend(QLatin1Char('/'));
77   }
78 }
79 
~SRUFetcher()80 SRUFetcher::~SRUFetcher() {
81   delete m_MARCXMLHandler;
82   m_MARCXMLHandler = nullptr;
83   delete m_MODSHandler;
84   m_MODSHandler = nullptr;
85   delete m_SRWHandler;
86   m_SRWHandler = nullptr;
87 }
88 
source() const89 QString SRUFetcher::source() const {
90   return m_name.isEmpty() ? defaultName() : m_name;
91 }
92 
93 // No Raw for now.
canSearch(Fetch::FetchKey k) const94 bool SRUFetcher::canSearch(Fetch::FetchKey k) const {
95   return k == Title || k == Person || k == ISBN || k == Keyword || k == LCCN;
96 }
97 
canFetch(int type) const98 bool SRUFetcher::canFetch(int type) const {
99   return type == Data::Collection::Book || type == Data::Collection::Bibtex;
100 }
101 
readConfigHook(const KConfigGroup & config_)102 void SRUFetcher::readConfigHook(const KConfigGroup& config_) {
103   m_host = config_.readEntry("Host");
104   int p = config_.readEntry("Port", SRU_DEFAULT_PORT);
105   if(p > 0) {
106     m_port = p;
107   }
108   m_path = config_.readEntry("Path");
109   // used to be called Database
110   if(m_path.isEmpty()) {
111     m_path = config_.readEntry("Database");
112   }
113   if(!m_path.startsWith(QLatin1Char('/'))) {
114     m_path.prepend(QLatin1Char('/'));
115   }
116   m_format = config_.readEntry("Format", "mods");
117   const QStringList queryFields = config_.readEntry("QueryFields", QStringList());
118   const QStringList queryValues = config_.readEntry("QueryValues", QStringList());
119   Q_ASSERT(queryFields.count() == queryValues.count());
120   for(int i = 0; i < qMin(queryFields.count(), queryValues.count()); ++i) {
121     m_queryMap.insert(queryFields.at(i), queryValues.at(i));
122   }
123 }
124 
search()125 void SRUFetcher::search() {
126   m_started = true;
127   if(m_host.isEmpty() || m_path.isEmpty() || m_format.isEmpty()) {
128     myDebug() << "settings are not set!";
129     stop();
130     return;
131   }
132 
133   QUrl u;
134   u.setScheme(QStringLiteral("http"));
135   u.setHost(m_host);
136   u.setPort(m_port);
137   u = QUrl::fromUserInput(u.url() + m_path);
138 
139   QUrlQuery query;
140   for(StringMap::ConstIterator it = m_queryMap.constBegin(); it != m_queryMap.constEnd(); ++it) {
141     query.addQueryItem(it.key(), it.value());
142   }
143   // allow user to override these so check for existing item first
144   if(!query.hasQueryItem(QStringLiteral("operation"))) {
145     query.addQueryItem(QStringLiteral("operation"), QStringLiteral("searchRetrieve"));
146   }
147   if(!query.hasQueryItem(QStringLiteral("version"))) {
148     query.addQueryItem(QStringLiteral("version"), QStringLiteral("1.1"));
149   }
150   if(!query.hasQueryItem(QStringLiteral("maximumRecords"))) {
151     query.addQueryItem(QStringLiteral("maximumRecords"), QString::number(SRU_MAX_RECORDS));
152   }
153   if(!m_format.isEmpty() && m_format != QLatin1String("none")
154      && !query.hasQueryItem(QStringLiteral("recordSchema"))) {
155     query.addQueryItem(QStringLiteral("recordSchema"), m_format);
156   }
157 
158   const int type = collectionType();
159   QString str = QLatin1Char('"') + request().value() + QLatin1Char('"');
160   switch(request().key()) {
161     case Title:
162       query.addQueryItem(QStringLiteral("query"), QLatin1String("dc.title=") + str);
163       break;
164 
165     case Person:
166       {
167         QString s;
168         if(type == Data::Collection::Book || type == Data::Collection::Bibtex) {
169           s = QLatin1String("author=") + str + QLatin1String(" or dc.author=") + str;
170         } else {
171           s = QLatin1String("dc.creator=") + str + QLatin1String(" or dc.editor=") + str;
172         }
173         query.addQueryItem(QStringLiteral("query"), s);
174       }
175       break;
176 
177     case ISBN:
178       {
179         QString s = request().value();
180         s.remove(QLatin1Char('-'));
181         QStringList isbnList = FieldFormat::splitValue(s);
182         // also search for isbn10 values
183         for(QStringList::Iterator it = isbnList.begin(); it != isbnList.end(); ++it) {
184           if((*it).startsWith(QLatin1String("978"))) {
185             QString isbn10 = ISBNValidator::isbn10(*it);
186             isbn10.remove(QLatin1Char('-'));
187             it = isbnList.insert(it, isbn10);
188             ++it;
189           }
190         }
191         QString q;
192         for(int i = 0; i < isbnList.count(); ++i) {
193           // make an assumption that DC output uses the dc profile and everything else uses Bath for ISBN
194           // no idea if this holds true universally, but matches LOC, COPAC, and KB
195           if(m_format == QLatin1String("dc")) {
196             q += QLatin1String("dc.identifier=") + isbnList.at(i);
197           } else {
198             q += QLatin1String("bath.isbn=") + isbnList.at(i);
199           }
200           if(i < isbnList.count()-1) {
201             q += QLatin1String(" or ");
202           }
203         }
204         query.addQueryItem(QStringLiteral("query"), q);
205       }
206       break;
207 
208     case LCCN:
209       {
210         QString s = request().value();
211         QStringList lccnList = FieldFormat::splitValue(s);
212         QString q;
213         for(int i = 0; i < lccnList.count(); ++i) {
214           q += QLatin1String("bath.lccn=") + lccnList.at(i);
215           q += QLatin1String(" or bath.lccn=") + LCCNValidator::formalize(lccnList.at(i));
216           if(i < lccnList.count()-1) {
217             q += QLatin1String(" or ");
218           }
219         }
220         query.addQueryItem(QStringLiteral("query"), q);
221       }
222       break;
223 
224     case Keyword:
225       query.addQueryItem(QStringLiteral("query"), str);
226       break;
227 
228     case Raw:
229       {
230         QString key = request().value().section(QLatin1Char('='), 0, 0).trimmed();
231         QString str = request().value().section(QLatin1Char('='), 1).trimmed();
232         query.addQueryItem(key, str);
233       }
234       break;
235 
236     default:
237       myWarning() << "key not recognized: " << request().key();
238       stop();
239       break;
240   }
241   u.setQuery(query);
242 //  myDebug() << u.url();
243 
244   m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
245   KJobWidgets::setWindow(m_job, GUI::Proxy::widget());
246   connect(m_job.data(), &KJob::result,
247           this, &SRUFetcher::slotComplete);
248 }
249 
stop()250 void SRUFetcher::stop() {
251   if(!m_started) {
252     return;
253   }
254   if(m_job) {
255     m_job->kill();
256     m_job = nullptr;
257   }
258 
259   m_started = false;
260   emit signalDone(this);
261 }
262 
slotComplete(KJob *)263 void SRUFetcher::slotComplete(KJob*) {
264   if(m_job->error()) {
265     m_job->uiDelegate()->showErrorMessage();
266     stop();
267     return;
268   }
269 
270   QByteArray data = m_job->data();
271   if(data.isEmpty()) {
272     stop();
273     return;
274   }
275   // see bug 319662. If fetcher is cancelled, job is killed
276   // if the pointer is retained, it gets double-deleted
277   m_job = nullptr;
278 
279 #if 0
280   myWarning() << "Remove debug from srufetcher.cpp";
281   QFile f(QString::fromLatin1("/tmp/test.xml"));
282   if(f.open(QIODevice::WriteOnly)) {
283     QTextStream t(&f);
284     t.setCodec("UTF-8");
285     t << data;
286   }
287   f.close();
288 #endif
289 
290   Data::CollPtr coll;
291   QString msg;
292 
293   const QString result = QString::fromUtf8(data.constData(), data.size());
294 
295   // first check for SRU errors
296   QDomDocument dom;
297   if(!dom.setContent(result, true /*namespace*/)) {
298     myWarning() << "server did not return valid XML.";
299     stop();
300     return;
301   }
302 
303   const QString& diag = XML::nsZingDiag;
304   QDomNodeList diagList = dom.elementsByTagNameNS(diag, QStringLiteral("diagnostic"));
305   for(int i = 0; i < diagList.count(); ++i) {
306     QDomElement elem = diagList.item(i).toElement();
307     QDomNodeList nodeList1 = elem.elementsByTagNameNS(diag, QStringLiteral("message"));
308     QDomNodeList nodeList2 = elem.elementsByTagNameNS(diag, QStringLiteral("details"));
309     for(int j = 0; j < nodeList1.count(); ++j) {
310       QString d = nodeList1.item(j).toElement().text();
311       if(!d.isEmpty()) {
312         QString d2 = nodeList2.item(j).toElement().text();
313         if(!d2.isEmpty()) {
314           d += QLatin1String(" (") + d2 + QLatin1Char(')');
315         }
316         myDebug() << "[" << m_host << "/" << m_path << "]" << d;
317         if(!msg.isEmpty()) {
318           msg += QLatin1Char('\n');
319         }
320         msg += d;
321       }
322     }
323   }
324 
325   QString modsResult;
326   if(m_format == QLatin1String("mods")) {
327     modsResult = result;
328 //  } else if(m_format == QLatin1String("marcxml") && initMARCXMLHandler()) {
329 // some SRU data sources call it MARC21-xml or something other than marcxml
330   } else if(m_format.startsWith(QLatin1String("marc"), Qt::CaseInsensitive) && initMARCXMLHandler()) {
331     // brute force marcxchange conversion. This is probably wrong at some level
332     QString newResult = result;
333     if(m_format.startsWith(QLatin1String("marcxchange"), Qt::CaseInsensitive)) {
334       newResult.replace(QRegularExpression(QLatin1String("xmlns:marc=\"info:lc/xmlns/marcxchange-v[12]\"")),
335                         QStringLiteral("xmlns:marc=\"http://www.loc.gov/MARC21/slim\""));
336     }
337     modsResult = m_MARCXMLHandler->applyStylesheet(newResult);
338   }
339   if(!modsResult.isEmpty() && initMODSHandler()) {
340     Import::TellicoImporter imp(m_MODSHandler->applyStylesheet(modsResult));
341     coll = imp.collection();
342     if(!msg.isEmpty()) {
343       msg += QLatin1Char('\n');
344     }
345     msg += imp.statusMessage();
346   } else if((m_format == QLatin1String("pam") ||
347              m_format == QLatin1String("dc") ||
348              m_format == QLatin1String("none")) &&
349             initSRWHandler()) {
350     Import::TellicoImporter imp(m_SRWHandler->applyStylesheet(result));
351     coll = imp.collection();
352     if(!msg.isEmpty()) {
353       msg += QLatin1Char('\n');
354     }
355     msg += imp.statusMessage();
356   } else {
357     myDebug() << "unrecognized format:" << m_format;
358     stop();
359     return;
360   }
361 
362   if(coll && !msg.isEmpty()) {
363     message(msg, coll->entryCount() == 0 ? MessageHandler::Warning : MessageHandler::Status);
364   }
365 
366   if(!coll) {
367     myDebug() << "no collection pointer";
368     if(!msg.isEmpty()) {
369       message(msg, MessageHandler::Error);
370     }
371     stop();
372     return;
373   }
374 
375   // since the Dewey and LoC field titles have a context in their i18n call here
376   // but not in the stylesheet where the field is actually created
377   // update the field titles here
378   QHashIterator<QString, QString> i(allOptionalFields());
379   while(i.hasNext()) {
380     i.next();
381     Data::FieldPtr field = coll->fieldByName(i.key());
382     if(field) {
383       field->setTitle(i.value());
384       coll->modifyField(field);
385     }
386   }
387 
388   foreach(Data::EntryPtr entry, coll->entries()) {
389     FetchResult* r = new FetchResult(this, entry);
390     m_entries.insert(r->uid, entry);
391     emit signalResultFound(r);
392   }
393   stop();
394 }
395 
fetchEntryHook(uint uid_)396 Tellico::Data::EntryPtr SRUFetcher::fetchEntryHook(uint uid_) {
397   return m_entries[uid_];
398 }
399 
updateRequest(Data::EntryPtr entry_)400 Tellico::Fetch::FetchRequest SRUFetcher::updateRequest(Data::EntryPtr entry_) {
401 //  myDebug() << source() << ": " << entry_->title();
402   QString isbn = entry_->field(QStringLiteral("isbn"));
403   if(!isbn.isEmpty()) {
404     return FetchRequest(Fetch::ISBN, isbn);
405   }
406 
407   QString lccn = entry_->field(QStringLiteral("lccn"));
408   if(!lccn.isEmpty()) {
409     return FetchRequest(Fetch::LCCN, lccn);
410   }
411 
412   // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
413   QString t = entry_->field(QStringLiteral("title"));
414   if(!t.isEmpty()) {
415     return FetchRequest(Fetch::Title, t);
416   }
417   return FetchRequest();
418 }
419 
initMARCXMLHandler()420 bool SRUFetcher::initMARCXMLHandler() {
421   if(m_MARCXMLHandler) {
422     return true;
423   }
424 
425   QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("MARC21slim2MODS3.xsl"));
426   if(xsltfile.isEmpty()) {
427     myWarning() << "can not locate MARC21slim2MODS3.xsl.";
428     return false;
429   }
430 
431   QUrl u = QUrl::fromLocalFile(xsltfile);
432 
433   m_MARCXMLHandler = new XSLTHandler(u);
434   if(!m_MARCXMLHandler->isValid()) {
435     myWarning() << "error in MARC21slim2MODS3.xsl.";
436     delete m_MARCXMLHandler;
437     m_MARCXMLHandler = nullptr;
438     return false;
439   }
440   return true;
441 }
442 
initMODSHandler()443 bool SRUFetcher::initMODSHandler() {
444   if(m_MODSHandler) {
445     return true;
446   }
447 
448   QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("mods2tellico.xsl"));
449   if(xsltfile.isEmpty()) {
450     myWarning() << "can not locate mods2tellico.xsl.";
451     return false;
452   }
453 
454   QUrl u = QUrl::fromLocalFile(xsltfile);
455 
456   m_MODSHandler = new XSLTHandler(u);
457   if(!m_MODSHandler->isValid()) {
458     myWarning() << "error in mods2tellico.xsl.";
459     delete m_MODSHandler;
460     m_MODSHandler = nullptr;
461     return false;
462   }
463   return true;
464 }
465 
initSRWHandler()466 bool SRUFetcher::initSRWHandler() {
467   if(m_SRWHandler) {
468     return true;
469   }
470 
471   QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("srw2tellico.xsl"));
472   if(xsltfile.isEmpty()) {
473     myWarning() << "can not locate srw2tellico.xsl.";
474     return false;
475   }
476 
477   QUrl u = QUrl::fromLocalFile(xsltfile);
478 
479   m_SRWHandler = new XSLTHandler(u);
480   if(!m_SRWHandler->isValid()) {
481     myWarning() << "error in srw2tellico.xsl.";
482     delete m_SRWHandler;
483     m_SRWHandler = nullptr;
484     return false;
485   }
486   return true;
487 }
488 
libraryOfCongress(QObject * parent_)489 Tellico::Fetch::Fetcher::Ptr SRUFetcher::libraryOfCongress(QObject* parent_) {
490   return Fetcher::Ptr(new SRUFetcher(i18n("Library of Congress (US)"), QStringLiteral("z3950.loc.gov"), 7090,
491                                      QStringLiteral("voyager"), QStringLiteral("mods"), parent_));
492 }
493 
defaultName()494 QString SRUFetcher::defaultName() {
495   return i18n("SRU Server");
496 }
497 
defaultIcon()498 QString SRUFetcher::defaultIcon() {
499 //  return QLatin1String("network-workgroup"); // just to be different than z3950
500   return QStringLiteral(":/icons/sru");
501 }
502 
503 // static
allOptionalFields()504 Tellico::StringHash SRUFetcher::allOptionalFields() {
505   StringHash hash;
506   hash[QStringLiteral("address")]  = i18n("Address");
507   hash[QStringLiteral("abstract")] = i18n("Abstract");
508   hash[QStringLiteral("dewey")]    = i18nc("Dewey Decimal classification system", "Dewey Decimal");
509   hash[QStringLiteral("lcc")]      = i18nc("Library of Congress classification system", "LoC Classification");
510   return hash;
511 }
512 
configWidget(QWidget * parent_) const513 Tellico::Fetch::ConfigWidget* SRUFetcher::configWidget(QWidget* parent_) const {
514   return new ConfigWidget(parent_, this);
515 }
516 
ConfigWidget(QWidget * parent_,const SRUFetcher * fetcher_)517 SRUFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const SRUFetcher* fetcher_ /*=0*/)
518     : Fetch::ConfigWidget(parent_) {
519   QGridLayout* l = new QGridLayout(optionsWidget());
520   l->setSpacing(4);
521   l->setColumnStretch(1, 10);
522 
523   int row = -1;
524   QLabel* label = new QLabel(i18n("Hos&t: "), optionsWidget());
525   l->addWidget(label, ++row, 0);
526   m_hostEdit = new GUI::LineEdit(optionsWidget());
527   connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
528   connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::signalName);
529   connect(m_hostEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotCheckHost);
530   l->addWidget(m_hostEdit, row, 1);
531   QString w = i18n("Enter the host name of the server.");
532   label->setWhatsThis(w);
533   m_hostEdit->setWhatsThis(w);
534   label->setBuddy(m_hostEdit);
535 
536   label = new QLabel(i18n("&Port: "), optionsWidget());
537   l->addWidget(label, ++row, 0);
538   m_portSpinBox = new QSpinBox(optionsWidget());
539   m_portSpinBox->setMaximum(999999);
540   m_portSpinBox->setMinimum(0);
541   m_portSpinBox->setValue(SRU_DEFAULT_PORT);
542   void (QSpinBox::* valueChanged)(int) = &QSpinBox::valueChanged;
543   connect(m_portSpinBox, valueChanged, this, &ConfigWidget::slotSetModified);
544   l->addWidget(m_portSpinBox, row, 1);
545   w = i18n("Enter the port number of the server. The default is %1.", SRU_DEFAULT_PORT);
546   label->setWhatsThis(w);
547   m_portSpinBox->setWhatsThis(w);
548   label->setBuddy(m_portSpinBox);
549 
550   label = new QLabel(i18n("Path: "), optionsWidget());
551   l->addWidget(label, ++row, 0);
552   m_pathEdit = new GUI::LineEdit(optionsWidget());
553   connect(m_pathEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
554   l->addWidget(m_pathEdit, row, 1);
555   w = i18n("Enter the path to the database used by the server.");
556   label->setWhatsThis(w);
557   m_pathEdit->setWhatsThis(w);
558   label->setBuddy(m_pathEdit);
559 
560   label = new QLabel(i18n("Format: "), optionsWidget());
561   l->addWidget(label, ++row, 0);
562   m_formatCombo = new GUI::ComboBox(optionsWidget());
563   m_formatCombo->addItem(QStringLiteral("MODS"), QLatin1String("mods"));
564   m_formatCombo->addItem(QStringLiteral("MARCXML"), QLatin1String("marcxml"));
565   m_formatCombo->addItem(QStringLiteral("PAM"), QLatin1String("pam"));
566   m_formatCombo->addItem(QStringLiteral("Dublin Core"), QLatin1String("dc"));
567   m_formatCombo->setEditable(true);
568   void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
569   connect(m_formatCombo, activatedInt, this, &ConfigWidget::slotSetModified);
570   connect(m_formatCombo, &QComboBox::editTextChanged, this, &ConfigWidget::slotSetModified);
571   l->addWidget(m_formatCombo, row, 1);
572   w = i18n("Enter the result format used by the server.");
573   label->setWhatsThis(w);
574   m_formatCombo->setWhatsThis(w);
575   label->setBuddy(m_formatCombo);
576 
577   l->setRowStretch(++row, 1);
578 
579   m_queryTree = new GUI::StringMapWidget(StringMap(), optionsWidget());
580   l->addWidget(m_queryTree, row, 0, 1, 2);
581   m_queryTree->setLabels(i18n("Field"), i18n("Value"));
582 
583   // now add additional fields widget
584   addFieldsWidget(SRUFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
585 
586   if(fetcher_) {
587     m_hostEdit->setText(fetcher_->m_host);
588     m_portSpinBox->setValue(fetcher_->m_port);
589     m_pathEdit->setText(fetcher_->m_path);
590     if(m_formatCombo->findData(fetcher_->m_format) == -1) {
591       m_formatCombo->addItem(fetcher_->m_format, fetcher_->m_format);
592     }
593     m_formatCombo->setCurrentData(fetcher_->m_format);
594     m_queryTree->setStringMap(fetcher_->m_queryMap);
595   }
596   KAcceleratorManager::manage(optionsWidget());
597 }
598 
saveConfigHook(KConfigGroup & config_)599 void SRUFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
600   QString s = m_hostEdit->text().trimmed();
601   if(!s.isEmpty()) {
602     config_.writeEntry("Host", s);
603   }
604   int port = m_portSpinBox->value();
605   if(port > 0) {
606     config_.writeEntry("Port", port);
607   }
608   s = m_pathEdit->text().trimmed();
609   if(!s.isEmpty()) {
610     config_.writeEntry("Path", s);
611   }
612   s = m_formatCombo->currentData().toString().trimmed();
613   if(s.isEmpty()) {
614     // user-entered format will not have data set for the item. Just use the text itself
615     s = m_formatCombo->currentText().trimmed();
616   }
617   if(!s.isEmpty()) {
618     config_.writeEntry("Format", s);
619   }
620   StringMap queryMap = m_queryTree->stringMap();
621   if(!queryMap.isEmpty()) {
622     config_.writeEntry("QueryFields", queryMap.keys());
623     config_.writeEntry("QueryValues", queryMap.values());
624   }
625 }
626 
preferredName() const627 QString SRUFetcher::ConfigWidget::preferredName() const {
628   QString s = m_hostEdit->text();
629   return s.isEmpty() ? SRUFetcher::defaultName() : s;
630 }
631 
slotCheckHost()632 void SRUFetcher::ConfigWidget::slotCheckHost() {
633   QString s = m_hostEdit->text();
634   // someone might be pasting a full URL, check that
635   if(s.indexOf(QLatin1Char(':')) > -1 || s.indexOf(QLatin1Char('/')) > -1) {
636     QUrl u(s);
637     if(u.isValid()) {
638       m_hostEdit->setText(u.host());
639       if(u.port() > 0) {
640         m_portSpinBox->setValue(u.port());
641       }
642       if(!u.path().isEmpty()) {
643         m_pathEdit->setText(u.path().trimmed());
644       }
645     }
646   }
647 }
648