chiark / gitweb /
Max gems should be 24 not 25
[ypp-sc-tools.main.git] / yarrg / convert.c
1 /*
2  * yarrg main program: argument parsing etc.
3  */
4 /*
5  *  This is part of ypp-sc-tools, a set of third-party tools for assisting
6  *  players of Yohoho Puzzle Pirates.
7  * 
8  *  Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
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 3 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, see <http://www.gnu.org/licenses/>.
22  * 
23  *  Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
24  *  are used without permission.  This program is not endorsed or
25  *  sponsored by Three Rings.
26  */
27
28 #include "convert.h"
29
30 const char *get_vardir(void) { return "."; }
31 const char *get_libdir(void) { return "."; }
32
33
34 enum outmodekind {
35   omk_unset, omk_raw, omk_str, omk_upload_yarrg, omk_upload_pctb
36 };
37
38 typedef struct {
39   enum outmodekind kind; /* unset is sentinel */
40   const char *str; /* non-0 iff not unset or raw */
41 } OutputMode;
42
43 static OutputMode o_outmodes[10];
44
45 static char *o_screenshot_fn;
46 static const char *o_serv_pctb, *o_serv_yarrg;
47 static const char *o_serv_dict_fetch, *o_serv_dict_submit;
48
49 const char *o_resolver= "./dictionary-manager";
50 FILE *screenshot_file;
51 const char *o_ocean, *o_pirate;
52 int o_quiet;
53
54 static pid_t screenshot_compressor=-1;
55
56 enum mode o_mode= mode_all;
57 enum flags o_flags=
58    ff_charset_allowedit |
59    ff_dict_fetch|ff_dict_submit|ff_dict_pirate;
60
61 static void vbadusage(const char *fmt, va_list) FMT(1,0) NORET;
62 static void vbadusage(const char *fmt, va_list al) {
63   fputs("bad usage: ",stderr);
64   vfprintf(stderr,fmt,al);
65   fputc('\n',stderr);
66   exit(12);
67 }
68 DEFINE_VWRAPPERF(static, badusage, NORET);
69
70 static void open_screenshot_file(int for_write) {
71   if (!fnmatch("*.gz",o_screenshot_fn,0)) {
72     int mode= for_write ? O_WRONLY|O_CREAT|O_TRUNC : O_RDONLY;
73     sysassert(! gzopen(o_screenshot_fn, mode, &screenshot_file,
74                        &screenshot_compressor, "-1") );
75   } else {
76     screenshot_file= fopen(o_screenshot_fn, for_write ? "w" : "r");
77     if (!screenshot_file)
78       fatal("could not open screenshots file `%s': %s",
79             o_screenshot_fn, strerror(errno));
80   }
81 }
82
83 static void run_analysis(void) {
84   FILE *tf;
85   OutputMode *om;
86
87   sysassert( tf= tmpfile() );
88   progress("running recognition...");
89   analyse(tf);
90
91   if (o_flags & ff_upload) {
92     if (o_flags & ff_singlepage)
93       fatal("Recognition successful, but refusing to upload partial data\n"
94             " (--single-page specified).  Specify an output mode?");
95   }
96
97   for (om=o_outmodes; om->kind != omk_unset; om++) {
98     sysassert( fseek(tf,0,SEEK_SET) == 0);
99
100     progress_log("processing results (--%s)...", om->str);
101     pid_t processor;
102     sysassert( (processor= fork()) != -1 );
103
104     if (!processor) {
105       sysassert( dup2(fileno(tf),0) ==0 );
106       if (om->kind==omk_raw) {
107         execlp("cat","cat",(char*)0);
108         sysassert(!"execute cat");
109       } else {
110         EXECLP_HELPER("commod-results-processor", om->str, (char*)0);
111       }
112     }
113
114     waitpid_check_exitstatus(processor, "output processor/uploader", 0);
115   }
116   
117   fclose(tf);
118   progress_log("all complete.");
119 }
120
121 static void rsync_core(const char *stem, const char *suffix,
122                        const char *zopt) {
123   pid_t fetcher;
124
125   progress("Updating dictionary %s...",stem);
126
127   sysassert( (fetcher= fork()) != -1 );
128   if (!fetcher) {
129     const char *rsync= getenv("YPPSC_YARRG_RSYNC");
130     if (!rsync) rsync= "rsync";
131   
132     const char *src= getenv("YPPSC_YARRG_DICT_UPDATE");
133     char *remote= masprintf("%s/master-%s.txt%s", src, stem, suffix);
134     char *local= masprintf("_master-%s.txt%s", stem, suffix);
135     if (DEBUGP(rsync))
136       fprintf(stderr,"executing rsync to fetch %s to %s\n",remote,local);
137     char *opts= masprintf("-Lt%s%s",
138                           zopt,
139                           DEBUGP(rsync) ? "v" : "");
140     execlp(rsync, "rsync",opts,"--",remote,local,(char*)0);
141     sysassert(!"exec rsync failed");
142   }
143
144   waitpid_check_exitstatus(fetcher, "rsync", 0);
145 }
146
147 void fetch_with_rsync_gz(const char *stem) { rsync_core(stem,".gz",""); }
148 void fetch_with_rsync(const char *stem) { rsync_core(stem,"","z"); }
149
150 static void get_timestamp(void) {
151   FILE *tf;
152   pid_t child;
153   sysassert( tf= tmpfile() );
154
155   sysassert( (child= fork()) != -1 );
156   if (!child) {
157     sysassert( dup2(fileno(tf),1)==1 );
158     EXECLP_HELPER("database-info-fetch","timestamp",(char*)0);
159   }
160   waitpid_check_exitstatus(child,"timestamp request",0);
161
162   sysassert( fseek(tf,0,SEEK_SET) == 0 );
163   static char lbuf[30];
164   int l= fread(lbuf,1,sizeof(lbuf),tf);
165   sysassert( !ferror(tf) );
166   assert( feof(tf) );
167   assert( l>1 );
168   l--;
169   assert( lbuf[l]=='\n' );
170   lbuf[l]= 0;
171
172   sysassert(! setenv("YPPSC_DATA_TIMESTAMP",lbuf,1) );
173   fclose(tf);
174 }
175
176 static void set_server(const char *envname, const char *defprotocol,
177                        const char *defvalue, const char *defvalue_test,
178                        const char *userspecified,
179                        int enable) {
180   const char *value;
181   
182   if (!enable) { value= "0"; goto ok; }
183
184   if (userspecified)
185     value= userspecified;
186   else if ((value= getenv(envname)))
187     ;
188   else if (o_flags & ff_testservers)
189     value= defvalue_test;
190   else
191     value= defvalue;
192
193   if (value[0]=='/' || (value[0]=='.' && value[1]=='/'))
194     /* absolute or relative pathname - or anyway, something with no hostname */
195     goto ok;
196
197   const char *colon= strchr(value, ':');
198   const char *slash= strchr(value, '/');
199
200   if (colon && (!slash || colon < slash))
201     /* colon before the first slash, if any */
202     /* rsync :: protocol specification - anyway, adding scheme:// won't help */
203     goto ok;
204
205   int vallen= strlen(value);
206
207   value= masprintf("%s%s%s", defprotocol, value,
208                    vallen && value[vallen-1]=='/' ? "" : "/");
209
210  ok:
211   sysassert(! setenv(envname,value,1) );
212 }
213
214 static void outputmode(enum outmodekind kind, const char *str) {
215   OutputMode *om= o_outmodes;
216   OutputMode *sentinel= o_outmodes + ARRAYSIZE(o_outmodes) - 1;
217   for (;;) {
218     if (om==sentinel) badusage("too many output modes specified");
219     if (!om->kind) break;
220     om++;
221   }
222   om->kind= kind;
223   om->str=  str;
224 }
225
226 static void outputmode_uploads(void) {
227   outputmode(omk_upload_yarrg, "upload-yarrg");
228   outputmode(omk_upload_pctb, "upload-pctb");
229 }
230
231 int main(int argc, char **argv) {
232   const char *arg;
233
234   sysassert( setlocale(LC_MESSAGES,"") );
235   sysassert( setlocale(LC_CTYPE,"en_GB.UTF-8") ||
236              setlocale(LC_CTYPE,"en_US.UTF-8") ||
237              setlocale(LC_CTYPE,"en.UTF-8") );
238
239 #define ARGVAL  ((*++argv) ? *argv : \
240                  (badusage("missing value for option %s",arg),(char*)0))
241
242 #define IS(s) (!strcmp(arg,(s)))
243
244   while ((arg=*++argv)) {
245     if (IS("--find-window-only"))      o_mode= mode_findwindow;
246     else if (IS("--screenshot-only"))  o_mode= mode_screenshot;
247     else if (IS("--show-charset"))     o_mode= mode_showcharset;
248     else if (IS("--analyse-only") ||
249              IS("--same"))             o_mode= mode_analyse;
250     else if (IS("--everything"))       o_mode= mode_all;
251     else if (IS("--find-island"))      o_flags |= ffs_printisland;
252     else if (IS("--single-page"))      o_flags |= ff_singlepage;
253     else if (IS("--quiet"))            o_quiet= 1;
254     else if (IS("--edit-charset"))     o_flags |= ff_charset_edit;
255     else if (IS("--no-edit-charset"))  o_flags &= ~(ffm_charset);
256     else if (IS("--test-servers"))     o_flags |= ff_testservers;
257     else if (IS("--dict-local-only"))  o_flags &= ~ffs_dict;
258     else if (IS("--dict-read-only"))   o_flags &= (~ffs_dict | ff_dict_fetch);
259     else if (IS("--dict-anon"))        o_flags &= ~ff_dict_pirate;
260     else if (IS("--dict-submit"))      o_flags |= ff_dict_fetch|ff_dict_submit;
261     else if (IS("--dict-no-update"))   o_flags &= ~ff_dict_fetch; // testing
262     else if (IS("--raw-tsv"))          outputmode(omk_raw,0);
263     else if (IS("--upload"))           outputmode_uploads();
264     else if (IS("--upload-yarrg"))     outputmode(omk_upload_yarrg,arg+2);
265     else if (IS("--upload-pctb"))      outputmode(omk_upload_pctb,arg+2);
266     else if (IS("--arbitrage") ||
267              IS("--tsv") ||
268              IS("--best-prices"))      outputmode(omk_str,arg+2);
269     else if (IS("--screenshot-file")||
270              IS("--screenshots-file")) o_screenshot_fn= ARGVAL;
271     else if (IS("--yarrg-server"))        o_serv_yarrg=       ARGVAL;
272     else if (IS("--pctb-server"))         o_serv_pctb=        ARGVAL;
273     else if (IS("--dict-submit-server"))  o_serv_dict_submit= ARGVAL;
274     else if (IS("--dict-update-server"))  o_serv_dict_fetch=  ARGVAL;
275     else if (IS("--ocean"))            o_ocean=  ARGVAL;
276     else if (IS("--pirate"))           o_pirate= ARGVAL;
277 #define DF(f)                                   \
278     else if (IS("-D" #f))                       \
279       debug_flags |= dbg_##f;
280     DEBUG_FLAG_LIST
281 #undef DF
282     else if (IS("--window-id")) {
283       char *ep;
284       unsigned long windowid= strtoul(ARGVAL,&ep,0);
285       if (*ep) badusage("invalid window id");
286       set_yppclient_window(windowid);
287     } else
288       badusage("unknown option `%s'",arg);
289   }
290
291   /* Consequential changes to options */
292
293   if (o_mode & mf_analyse) {
294     if (!o_outmodes[0].kind) {
295       if (o_flags & ff_printisland) {
296         o_flags |= ff_singlepage;
297       } else {
298         outputmode_uploads();
299       }
300     }
301   } else {
302     if (o_outmodes[0].kind)
303       badusage("overall mode does not include analysis but output option(s)"
304                " (eg `--%s') specified",  o_outmodes[0].str);
305   }
306
307   OutputMode *om;
308   for (om=o_outmodes; om->kind; om++) {
309     switch (om->kind) {
310     case omk_upload_yarrg: o_flags |= ffs_upload | ff_use_yarrg; break;
311     case omk_upload_pctb:  o_flags |= ffs_upload | ff_use_pctb;  break;
312     default: ;
313     }
314   }
315
316   if ((o_flags & (ff_needisland|ff_upload)) &&
317       !(o_flags & (ffm_use)))
318     o_flags |= ffm_use; /* all */
319
320   if (o_serv_yarrg && !o_serv_dict_submit)
321     o_serv_dict_submit= o_serv_yarrg;
322   
323   /* Defaults */
324
325   set_server("YPPSC_YARRG_YARRG",
326              "http://",          "upload.yarrg.chiark.net",
327                                  "upload.yarrg.chiark.net/test",
328              o_serv_yarrg,       o_flags & ff_use_yarrg);
329
330   set_server("YPPSC_YARRG_PCTB",
331              "http://",          "pctb.crabdance.com",
332                                  "pctb.ilk.org",
333              o_serv_pctb,        o_flags & ff_use_pctb);
334              
335   set_server("YPPSC_YARRG_DICT_UPDATE",
336              "rsync://",         "rsync.yarrg.chiark.net/yarrg",
337                                  "rsync.yarrg.chiark.net/yarrg/test",
338              o_serv_dict_fetch,   o_flags & ff_dict_fetch);
339
340   set_server("YPPSC_YARRG_DICT_SUBMIT",
341              "http://",           "upload.yarrg.chiark.net",
342                                   "upload.yarrg.chiark.net/test",
343              o_serv_dict_submit,  o_flags & ff_dict_submit);
344
345   if (!o_screenshot_fn)
346     o_screenshot_fn= masprintf("%s/_pages.ppm.gz", get_vardir());
347
348   /* Actually do the work */
349
350   if ((o_flags & ff_upload) && (o_flags & ff_use_yarrg))
351     get_timestamp();
352              
353   canon_colour_prepare();
354   
355   if (o_mode & mf_findwindow) {
356     screenshot_startup();
357     find_yppclient_window();
358   }
359   if (!ocean)  ocean=  o_ocean;
360   if (!pirate) pirate= o_pirate;
361   
362   if (o_flags & ff_needisland)
363     if (!ocean)
364       badusage("need --ocean option when not using actual YPP client window"
365                " (consider supplying --pirate too)");
366   if (ocean)
367     sysassert(! setenv("YPPSC_OCEAN",ocean,1) );
368   if (pirate && (o_flags & ff_dict_pirate))
369     sysassert(! setenv("YPPSC_PIRATE",pirate,1) );
370
371   switch (o_mode & mfm_special) {
372   case 0:                                      break;
373   case mode_showcharset:  ocr_showcharsets();  exit(0);
374   default:                                     abort();
375   }
376
377   if (o_mode & mf_printoceanpirate)
378     printf("%s %s\n",ocean,pirate);
379
380   if (o_mode & mf_screenshot) {
381     open_screenshot_file(1);
382     if (o_flags & ff_singlepage) take_one_screenshot();
383     else take_screenshots();
384     progress_log("OK for you to move the mouse now, and you can"
385                  " use the YPP client again.");
386     progress("Finishing handling screenshots...");
387     gzclose(&screenshot_file,&screenshot_compressor,"screenshots output");
388   }
389   if (o_mode & mf_readscreenshot) {
390     if ((o_flags & ff_upload) && !(o_flags & ff_testservers))
391       badusage("must not reuse screenshots for upload to live databases");
392     open_screenshot_file(0);
393     if (o_flags & ff_singlepage) read_one_screenshot();
394     else read_screenshots();
395     gzclose(&screenshot_file,&screenshot_compressor,"screenshots input");
396   }
397   if (o_mode & mf_analyse) {
398     if (o_flags & ff_needisland) {
399       find_islandname();
400       if (o_flags & ff_printisland)
401         printf("%s, %s\n", archipelago, island);
402       sysassert(! setenv("YPPSC_ISLAND",island,1) );
403     }
404     if (o_outmodes[0].kind==omk_raw && o_outmodes[1].kind==omk_unset)
405       analyse(stdout);
406     else
407       run_analysis();
408   }
409   progress_log("Finished.");
410   return 0;
411 }
412
413
414 FILE *dbfile;
415 static const char *basepath; /* as passed in by caller */
416 static pid_t dbzcat;
417
418 int dbfile_gzopen(const char *basepath_spec) {
419   assert(!dbfile);
420
421   basepath= basepath_spec;
422
423   char *zpath= masprintf("%s.gz", basepath);
424   int e= gzopen(zpath, O_RDONLY, &dbfile, &dbzcat, 0);
425   free(zpath);
426   if (e) { errno=e; sysassert(errno==ENOENT); return 0; }
427   
428   return 1;
429 }  
430
431 int dbfile_open(const char *tpath) {
432   assert(!dbfile);
433
434   basepath= tpath;
435
436   dbzcat= -1;
437   dbfile= fopen(tpath,"r");
438   if (!dbfile) { sysassert(errno==ENOENT); return 0; }
439   return 1;
440 }  
441
442 void dbfile_close(void) {
443   gzclose(&dbfile, &dbzcat, basepath);
444 }
445
446 #define dbassertgl(x) ((x) ? (void)0 : dbfile_assertfail(file,line,#x))
447
448 void dbfile_getsline(char *lbuf, size_t lbufsz, const char *file, int line) {
449   errno=0;
450   char *s= fgets(lbuf,lbufsz,dbfile);
451   sysassert(!ferror(dbfile));
452   dbassertgl(!feof(dbfile));
453   assert(s);
454   int l= strlen(lbuf);
455   dbassertgl(l>0);  dbassertgl(lbuf[--l]=='\n');
456   lbuf[l]= 0;
457 }
458
459 int dbfile_vscanf(const char *fmt, va_list al) {
460   int r= vfscanf(dbfile,fmt,al);
461   sysassert(!ferror(dbfile));
462   return r;
463 }
464
465 int dbfile_scanf(const char *fmt, ...) {
466   va_list al;
467   va_start(al,fmt);
468   int r= dbfile_vscanf(fmt,al);
469   va_end(al);
470   return r;
471 }
472
473 void dbfile_assertfail(const char *file, int line, const char *m) {
474   if (dbzcat)
475     fatal("Error in dictionary file %s.gz:\n"
476           " Requirement not met at %s:%d:\n"
477           " %s",
478           basepath, file,line, m);
479   else if (dbfile)
480     fatal("Error in dictionary file %s at byte %ld:\n"
481           " Requirement not met at %s:%d:\n"
482           " %s",
483           basepath,(long)ftell(dbfile), file,line, m);
484   else
485     fatal("Semantic error in dictionaries:\n"
486           " Requirement not met at %s:%d:\n"
487           " %s",
488           file,line, m);
489 }
490
491 int gzopen(const char *zpath, int oflags, FILE **f_r, pid_t *pid_r,
492            const char *gziplevel /* 0 for read; may be 0, or "-1" etc. */) {
493
494   int zfd= open(zpath, oflags, 0666);
495   if (zfd<0) return errno;
496
497   int pipefds[2];
498   sysassert(! pipe(pipefds) );
499
500   int oi,io; const char *cmd; const char *stdiomode;
501   switch ((oflags & O_ACCMODE)) {
502   case O_RDONLY: oi=0; io=1; cmd="gunzip"; stdiomode="r"; break;
503   case O_WRONLY: oi=1; io=0; cmd="gzip";   stdiomode="w"; break;
504   default: abort();
505   }
506
507   sysassert( (*pid_r=fork()) != -1 );
508   if (!*pid_r) {
509     sysassert( dup2(zfd,oi)==oi );
510     sysassert( dup2(pipefds[io],io)==io );
511     sysassert(! close(zfd) );
512     sysassert(! close(pipefds[0]) );
513     sysassert(! close(pipefds[1]) );
514     execlp(cmd,cmd,gziplevel,(char*)0);
515     sysassert(!"execlp gzip/gunzip");
516   }
517   sysassert(! close(zfd) );
518   sysassert(! close(pipefds[io]) );
519   sysassert( *f_r= fdopen(pipefds[oi], stdiomode) );
520
521   return 0;
522 }
523
524 void gzclose(FILE **f, pid_t *p, const char *what) {
525   if (!*f) return;
526   
527   sysassert(!ferror(*f));
528   sysassert(!fclose(*f));
529
530   if (*p != -1) {
531     char *process= masprintf("%s (de)compressor",what);
532     waitpid_check_exitstatus(*p,process,1);
533     free(process);
534     *p= -1;
535   }
536
537   *f= 0;
538 }