source: trunk/gsdl/src/recpt/z3950proto.cpp@ 1453

Last change on this file since 1453 was 1453, checked in by jrm21, 24 years ago

Added check to only free "icon" and "smallicon" string resources if they
were set... :( (stupid, stupid....)

  • Property svn:keywords set to Author Date Id Revision
File size: 18.8 KB
Line 
1/**********************************************************************
2 *
3 * z3950proto.cpp --
4 * Copyright (C) 2000 The New Zealand Digital Library Project
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
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, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *********************************************************************/
25
26#include "z3950proto.h"
27#include "comtypes.h"
28#include "browsefilter.h"
29#include "queryfilter.h"
30#include "filter.h"
31// z39.50 yaz stuff
32#include <stdio.h> // for (FILE *) type for yyin and fopen.
33// config file parsing stuff
34#include "z3950cfg.h" // for reading in config files -
35// defines "struct z3950cfg *zserver_list" as the head of the list.
36extern FILE *yyin;
37extern "C" {
38 extern int zconfigparse();
39}
40
41
42
43
44z3950proto::z3950proto() {
45 zserver_count=0;
46}
47
48z3950proto::~z3950proto() {
49}
50
51void z3950proto::add_server (z3950_server& zserver) {
52
53 // append the new server
54 zserver_count++;
55 zservers.push_back(&zserver);
56}
57
58void z3950proto::read_config_file(const text_t &filename, const text_t &errf) {
59 struct z3950cfg *here;
60 struct z3950cfg *oldhere;
61 z3950_server *zserver;
62 ShortColInfo_t *tempinfo;
63
64 // FILE *errfile declared in z3950cfg.h, defined in zparse.y
65
66 if ((errfile=fopen(errf.getcstr(),"a"))==NULL) {
67 // what do we do if we can't open the error file?
68 // this means that errors will go to stderr, which may stuff up
69 // any cgi headers and the page.
70 errfile=stderr;
71 }
72
73 // zconfigparse() is defined in zparse.tab.c,
74 // which is the bison output of zparse.y
75
76 yyin=fopen(filename.getcstr(),"r");
77 if (yyin==NULL) {
78 cerr << "Could not open "<<filename.getcstr()<<" for reading.\n";
79 return;
80 }
81 zconfigparse();
82
83 if (errfile!=stderr)
84 fclose(errfile);
85
86 // we now have the config files in the ptr zserver_list
87 if (zserver_list==NULL)
88 return; // no valid servers found in the config file - note that
89 // the parser will have already spat out any errors.
90
91 // now create z3950servers for each structure in server_list
92 here=zserver_list;
93 while (here!=NULL) {
94 zserver=new z3950_server;
95 tempinfo=new ShortColInfo_t;
96
97 tempinfo->host.setcstr(here->hostname);
98 tempinfo->port=here->port;
99 tempinfo->name.setcstr(here->dbname);
100 zserver->setInfo(tempinfo);
101 zserver->setName(here->shortname);
102 // now collection metadata.
103 zserver->setMeta("collectionname",here->longname);
104 if (here->icon!=NULL)
105 zserver->setMeta("iconcollection",here->icon);
106 if (here->smallicon!=NULL)
107 zserver->setMeta("iconcollectionsmall",here->smallicon);
108
109 /* filterclass *filter = new filterclass ();
110 zserver->add_filter (filter);
111 browsefilterclass *browsefilter = new browsefilterclass();
112 zserver->add_filter (browsefilter);
113 queryfilterclass *queryfilter = new queryfilterclass();
114 zserver->add_filter (queryfilter);
115 */
116
117 // About list
118 if (here->about!=NULL) {
119 struct z3950aboutlist *about_here=here->about;
120 struct z3950aboutlist *oldabout;
121
122 while (about_here!=NULL) {
123 // problem with default lang (null): can't add ("",..)
124 if (about_here->lang==NULL)
125 zserver->addcfgAbout("en",about_here->text);
126 else
127 zserver->addcfgAbout(about_here->lang, about_here->text);
128 oldabout=about_here;
129 about_here=about_here->next;
130 free(oldabout->lang);
131 free(oldabout->text);
132 free(oldabout);
133 }
134 }
135
136 oldhere=here;
137 here=here->next;
138 free(oldhere->shortname); // these 4 strings should all be non-NULL...
139 free(oldhere->hostname);
140 free(oldhere->dbname);
141 free(oldhere->longname);
142 if (oldhere->icon) free(oldhere->icon); // these 2 may be NULL
143 if (oldhere->smallicon) free(oldhere->smallicon);
144 free(oldhere);
145
146 add_server(*zserver);
147 } // end of while loop.
148
149}
150
151void z3950proto::configure (const text_t &/*key*/,
152 const text_tarray &/*cfgline*/) {
153 // this is called for each line in the gsdlsite.cfg file
154}
155
156
157bool z3950proto::init (ostream &/*logout*/) {
158 // set up tcp connection to server here?
159 // we might also read in the config file here (instead of librarymain.cpp)
160
161 //
162
163 // logout goes to initout.txt
164 // logout <<"zdebug:init:Number of z3950 servers: "<< zserver_count << "\n";
165 //logout << "\t1st server name: " << zservers[0].getName().getcstr() << "\n";
166 return true;
167
168}
169
170/*text_t z3950proto::get_protocol_name () {
171 return "z3950proto";
172}
173*/
174
175void z3950proto::get_collection_list (text_tarray &collist,
176 comerror_t &/*err*/,
177 ostream &/*logout*/) {
178
179 z3950_server_array::iterator here = zservers.begin();
180 z3950_server_array::iterator end = zservers.end();
181 while (here != end) {
182 collist.push_back((*here)->getName());
183 here++;
184 }
185}
186
187void z3950proto::has_collection (const text_t &collection, bool &hascollection,
188 comerror_t &/*err*/, ostream &/*logout*/) {
189 z3950_server_array::iterator here = zservers.begin();
190 z3950_server_array::iterator end = zservers.end();
191 while (here != end) {
192 if((*here)->getName()==collection) {
193 hascollection=true;
194 return;
195 }
196 here++;
197 }
198 hascollection=false;
199}
200
201void z3950proto::ping (const text_t &/*collection*/, bool &wassuccess,
202 comerror_t &/*err*/, ostream &/*logout*/) {
203 // should we just ping the server, or actually create a connection
204 // to the z39.50 server process on the machine ?
205 wassuccess = true;
206}
207
208void z3950proto::get_collectinfo (const text_t &collection,
209 ColInfoResponse_t &collectinfo,
210 comerror_t &err, ostream &logout) {
211
212 // set err to protocolError if something goes wrong...
213 err=noError;
214
215 z3950_server_array::iterator here = zservers.begin();
216 z3950_server_array::iterator end = zservers.end();
217 while (here != end) {
218 if((*here)->getName()==collection) {
219 break;
220 }
221 here++;
222 }
223
224 if (here==end) {
225 err=protocolError;
226 logout << "z39.50: couldn't find collection"
227 << collection.getcstr()
228 << endl;
229 return;
230 }
231
232 const ShortColInfo_t *colinfo=(*here)->getInfo();
233 collectinfo.shortInfo.name=colinfo->name;
234 collectinfo.shortInfo.host=colinfo->host;
235 collectinfo.shortInfo.port=colinfo->port;
236
237 collectinfo.isPublic=true;
238 // don't use beta field
239 /*collectinfo.isBeta=false;*/
240 collectinfo.buildDate=1;
241 // leave ccsCols empty (no cross-coll. searching - for now)
242 /*collectinfo.ccsCols=(text_tarray);*/ //not like this!!!
243 // This info is available from the config file -- johnmcp
244 /*******collectinfo.languages.push_back("en");
245 collectinfo.languages.push_back("fr");********/
246 collectinfo.numDocs=0;
247 collectinfo.numWords=0;
248 collectinfo.numBytes=0;
249 // copy the text maps over.
250 // collectinfo.collectionmeta; // text_tmap
251 collectinfo.collectionmeta=*((*here)->getMeta());
252 collectinfo.format=*((*here)->getFormat()); //text_tmap
253 /* collectinfo.building; //text_tmap */
254
255 ////collectinfo.receptionist="z3950";
256 /* for now... this is a url, relative to .../cgi-bin.
257 NOTE: if this is empty, it defaults to _gwcgi_?a=p&p=about&c=<colname>
258 */
259}
260
261void z3950proto::get_filterinfo (const text_t &/*collection*/,
262 InfoFiltersResponse_t &response,
263 comerror_t &/*err*/, ostream &/*logout*/) {
264 // we'll fake it here, and say we have set up some filters
265 response.filterNames.insert("BrowseFilter");
266 response.filterNames.insert("QueryFilter");
267 response.filterNames.insert("NullFilter");
268
269}
270
271void z3950proto::get_filteroptions (const text_t &/*collection*/,
272 const InfoFilterOptionsRequest_t &/*req*/,
273 InfoFilterOptionsResponse_t &response,
274 comerror_t &err, ostream &/*logout*/) {
275 // for now, assume all servers have the same characteristics
276 /* if (request.filterName=="QueryFilter") { }
277 else if (request.filterName=="BrowseFilter") { }
278 else if (request.filterName=="NullFilter") { } */
279 response.filterOptions["Index"].type=FilterOption_t::stringt;
280 response.filterOptions["Index"].repeatable=FilterOption_t::onePerQuery;
281 response.filterOptions["Index"].defaultValue="any";
282 response.filterOptions["Index"].validValues.push_back(".any");
283 response.filterOptions["Index"].validValues.push_back(".title");
284 response.filterOptions["Index"].validValues.push_back(".author");
285 // and maybe ["Language"] option as well?
286 err=noError;
287}
288
289void z3950proto::filter (const text_t &collection,
290 FilterRequest_t &request,
291 FilterResponse_t &response,
292 comerror_t &err, ostream &logout) {
293 // this function is called when:
294 // * creating the title page,(looking for iconcoll* & collectname metadata)
295 // * creating the about page (looking for "Title" metadata)
296 // * doing the query - (note that a request for metadata comes first, then
297 // filterOptions = FRmetadata | FROID | FRtermFreq (64+4+1)
298
299 // metadata-only requests have filterName="NullFilter", else "QueryFilter".
300 // For the title page, we should not create a connection to the target
301 // (target means the actual z39.50 server, origin means us), but
302 // for the about page and query pages, we need to get information from the
303 // origin. (eg for the about page, we will print out some info such as ID,
304 // name and version.
305
306 // cerr now goes to errout.txt in etc directory
307 err=noError;
308
309 // get relevant "collection"
310 z3950_server_array::iterator zserver = zservers.begin();
311 z3950_server_array::iterator zend = zservers.end();
312 while (zserver != zend) {
313 if((*zserver)->getName()==collection) {
314 break;
315 }
316 zserver++;
317 }
318 // now have collection in zserver.
319
320 ColInfoResponse_t info;
321 ResultDocInfo_t *docInfo;
322
323 // leave response.termInfo empty
324 // response.termInfo.push_back(""); ??????? (should be empty if not req.)
325
326 // See if this is for a query action
327 if (request.filterName=="QueryFilter") {
328 /* Sample OptionValue pairs
329 `StartResults'=`1'
330 `EndResults'=`20'
331 `Term'=`firstword secondword' (term is just whatever the user typed in)
332 `QueryType'=`ranked' => 'OR' (cgiarg t=1)
333 `QueryType' = `boolean' => 'AND' (cgiarg t=0)
334 `Casefold'=`true'
335 `Stem'=`false'
336 `Maxdocs'=`50'
337 */
338 // go through options
339 text_t opt_term; // the term(s) that the user entered
340 text_t opt_fields; // which fields to search on
341 int opt_start=1, opt_end=20; // default values
342 int nummatches=0, maxdocs=50; // default values
343 OptionValue_tarray::iterator ov_here=request.filterOptions.begin();
344 OptionValue_tarray::iterator ov_end=request.filterOptions.end();
345 while (ov_here != ov_end) {
346 // cout << "OV pair: `" << ov_here->name.getcstr() << "'=`"
347 // << ov_here->value.getcstr() << "'\n";
348 if (ov_here->name=="Term")
349 {
350 opt_term=ov_here->value;
351 } else if (ov_here->name=="Index")
352 {
353 opt_fields=ov_here->value;
354 } else if (ov_here->name=="StartResults")
355 {
356 opt_start=ov_here->value.getint();
357 } else if (ov_here->name=="EndResults")
358 {
359 opt_end=ov_here->value.getint();
360 } else if (ov_here->name=="Maxdocs")
361 {
362 maxdocs=ov_here->value.getint();
363 }
364 ov_here++;
365 }
366 err=noError;
367 text_tarray *titles=(*zserver)->getrecordTitles(opt_term,
368 opt_fields,
369 opt_start, // first to get
370 opt_end-opt_start, //count
371 &nummatches,err);
372 if (err!=noError) {
373 // can we return an err msg in a response, or just use
374 // the more drastic Greenstone error mechanism?
375 docInfo=new ResultDocInfo_t;
376 response.docInfo.push_back(*docInfo);
377 docInfo->metadata["Title"].values.push_back("Error - query err?");
378 logout << "\nz3950 filter query: error connecting to server\n";
379 // for now, DON'T use GSDL protocol err.
380 err=noError;
381 return;
382 }
383 // check if (titles==NULL) - only happens on error?
384 if (nummatches>0) {
385 text_tarray::iterator titles_here=titles->begin();
386 text_tarray::iterator titles_end=titles->end();
387 int counter=1;
388 while (titles_here!=titles_end) {
389 docInfo=new ResultDocInfo_t;
390 docInfo->metadata["Title"].values.push_back(*titles_here);
391 docInfo->result_num=counter;
392 // we need to give some OID, so we'll just use counter for now...
393 // make it the number into the whole possible retrieved set.
394 docInfo->OID=counter+opt_start-1;
395 response.docInfo.push_back(*docInfo);
396 counter++;
397 titles_here++;
398 }
399 }
400
401 if (request.filterResultOptions & FRtermFreq) {
402 if (nummatches>maxdocs) {
403 response.numDocs=maxdocs; // eg "more than 50" (if Maxdocs==50)
404 response.isApprox=MoreThan;
405 } else {
406 response.numDocs=nummatches; // eg "36 documents"
407 response.isApprox=Exact; // Exact | Approximate | MoreThan
408 }
409 } // end of if (... & FRtermFreq)
410
411 } // end of if (... & FROID)
412 else {
413 // this wasn't a query action
414
415 if (request.filterOptions.size()>0 &&
416 request.filterOptions[0].name=="ParentNode") {
417 // don't want to return anything
418 return;
419 /* } else if (request.docSet.size() &&
420 request.docSet[0]!="collection") {
421 // documentaction
422 // if docSet is not empty, it is either "collection", or an array
423 // of OIDs
424 docInfo=new ResultDocInfo_t;
425 response.docInfo.push_back(*docInfo);
426 */
427 } else {
428 // in case we need to return only metadata
429 docInfo=new ResultDocInfo_t;
430 response.docInfo.push_back(*docInfo);
431 }
432 }
433
434 // Fill in metadata for each response.docInfo (if wanted)
435 if (request.filterResultOptions & FRmetadata) {
436 get_collectinfo (collection, info, err, logout);
437 // should check err returned here....
438
439 // get the Query out of the filterOptions.
440 text_t query="";
441 text_t field="";
442 OptionValue_tarray::iterator opt_here=request.filterOptions.begin();
443 OptionValue_tarray::iterator opt_end=request.filterOptions.end();
444 while (opt_here!=opt_end) {
445 if (opt_here->name=="Query") {
446 query=opt_here->value;
447 if (field!="") break; // break from loop if we've got both
448 } else if (opt_here->name=="Index") {
449 field=opt_here->value;
450 if (query!="") break; // break from loop if we've got both
451 }
452 opt_here++;
453 }
454
455 if (!request.fields.empty()) {
456 // loop on each document being returned
457 ResultDocInfo_tarray::iterator docs_here=response.docInfo.begin();
458 ResultDocInfo_tarray::iterator docs_end=response.docInfo.end();
459 while (docs_here!=docs_end) {
460
461 // loop on all the metadata fields in request.fields (type text_tset)
462 text_tset::iterator fields_here=request.fields.begin();
463 text_tset::iterator fields_end=request.fields.end();
464 text_tmap::iterator it;
465 while (fields_here!=fields_end) {
466 it=info.collectionmeta.find(*fields_here);
467 if (it!=info.collectionmeta.end())
468 docs_here->metadata[*fields_here].values.push_back((*it).second);
469 else if (*fields_here=="Title" && !request.docSet.empty()) {
470 // We only do this for a document action.
471 // (This comes through as a NullQuery).
472 // hopefully docSet is only not empty for documentaction...
473 text_t doctitle;
474 int i;
475 // check that docSet isn't empty first!!!!!!
476 i=request.docSet[0].getint();
477 text_t doctext="unneeded";
478 (*zserver)->getfullrecord(query, field, i, doctitle, doctext, err);
479 // check err value!
480 docs_here->metadata["Title"].values.push_back(doctitle);
481 } else {
482 docs_here->metadata[*fields_here].values.push_back("");
483 /////// cerr << " (not found)";
484 }
485 fields_here++;
486 } // end of inner while loop
487 docs_here++;
488 } // end of outer while loop
489 } // end of if (!request.fields.empty())
490
491 else { // request.fields empty: return all metadata for about page or query
492 // we'll only put it in the first docInfo.
493 text_tmap::iterator colmeta_here=info.collectionmeta.begin();
494 text_tmap::iterator colmeta_end=info.collectionmeta.end();
495 while (colmeta_here!=colmeta_end) {
496 response.docInfo[0].metadata[(*colmeta_here).first].
497 values.push_back((*colmeta_here).second);
498 colmeta_here++;
499 }
500
501 // check if "collectionextra" metadata is set. If it isn't, we should
502 // create connection to target to get it.
503 if (info.collectionmeta.find("collectionextra")==colmeta_end) {
504 // it hasn't been set yet...
505 text_t abouttext="<B>Server Online</B><br>\n";
506 abouttext+=(*zserver)->getzAbout();
507 // add in the "About" text we read in from config file.
508 // how do we incorporate multi-lingual metadata?
509 abouttext+="<P>\n";
510 text_t tmpabout;
511
512 if ((*zserver)->getcfgAbout("en", tmpabout)==true)
513 abouttext+=tmpabout;
514
515 (*zserver)->setMeta("collectionextra",abouttext);
516 response.docInfo[0].metadata["collectionextra"].values.push_back(abouttext);
517 }
518 } // end of else
519
520 // do indices' names, regardless of whether asked for or not...
521 if (!response.docInfo.empty()) {
522 response.docInfo[0].metadata[".author"].values.push_back("author fields");
523 response.docInfo[0].metadata[".title"].values.push_back("title fields");
524 response.docInfo[0].metadata[".any"].values.push_back("any fields");
525 }
526 } //end of if (... & FRmetadata) ...
527}
528
529
530
531void z3950proto::get_document (const text_t &collection,
532 const DocumentRequest_t &request,
533 DocumentResponse_t &response,
534 comerror_t &err, ostream &logout) {
535
536 err=noError;
537
538 // get relevant "collection"
539 z3950_server_array::iterator zserver = zservers.begin();
540 z3950_server_array::iterator zend = zservers.end();
541 while (zserver != zend) {
542 if((*zserver)->getName()==collection) {
543 break;
544 }
545 zserver++;
546 }
547 // now have collection in zserver.
548
549 /* cout << "get document:\n\tOID: " << request.OID.getcstr()
550 << "\n\tdocType: " << request.docType.getcstr()
551 << "\n\tdocFormat: " << request.docFormat.getcstr() <<"\n";
552 */
553
554 /* docresponse consists of
555 text_t response.doc */
556 text_t title="unneeded";
557 text_t doctext;
558 text_t query; // this should not be needed, as we have already connected to
559 // get the title....
560 text_t field; // ditto...
561 (*zserver)->getfullrecord(query,field,request.OID.getint(),
562 title,doctext,err);
563 // check return value of above? (false=>not connected)
564 if (err==noError)
565 response.doc=doctext;
566 else {
567 // could print out different messages based on error type....
568 response.doc="<h2>Error</h2>There was an error while connecting to the ";
569 response.doc+="z39.50 server (ie target). Most likely this was a \n";
570 response.doc+="\"Connection Refused\" error.\n";
571
572 }
573 if (0) {
574 err=protocolError;
575 logout << "Some error\n";
576 }
577}
Note: See TracBrowser for help on using the repository browser.