]> 4ch.mooo.com Git - 16.git/blob - 16/adplug/adplug/adplugdb/adplugdb.cpp
wwww~
[16.git] / 16 / adplug / adplug / adplugdb / adplugdb.cpp
1 /*
2  * AdPlug - Replayer for many OPL2/OPL3 audio file formats.
3  * Copyright (c) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al.
4  * 
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  * 
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  * 
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * adplugdb.cpp - AdPlug database maintenance utility
20  * Copyright (c) 2002 Riven the Mage <riven@ok.ru>
21  * Copyright (c) 2002, 2003, 2006 Simon Peter <dn.tlp@gmx.net>
22  */
23
24 #include <memory.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <stdarg.h>
28 #include <string.h>
29 #include <binfile.h>
30 #include <string>
31 #include <iostream>
32
33 #if defined(HAVE_SYS_TYPES_H) && defined(HAVE_SYS_STAT_H)
34 #  if HAVE_SYS_TYPES_H
35 #    include <sys/types.h>
36 #  endif
37 #  if HAVE_SYS_STAT_H
38 #    include <sys/stat.h>
39 #  endif
40 #endif
41
42 #ifndef HAVE_MKDIR
43 #  define HAVE_MKDIR  0
44 #endif
45
46 #include "../src/adplug.h"
47 #include "../src/silentopl.h"
48 #include "../src/database.h"
49
50 /*
51  * Apple (OS X) and Sun systems declare getopt in unistd.h, other systems
52  * (Linux) use getopt.h.
53  */
54 #if defined (__APPLE__) || (defined(__SVR4) && defined(__sun))
55 #       include <unistd.h>
56 #else
57 #       ifdef HAVE_GETOPT_H
58 #               include <getopt.h>
59 #       else
60 #               include "mygetopt.h"
61 #       endif
62 #endif
63
64 /***** Defines *****/
65
66 // Default file name of AdPlug's database file
67 #define ADPLUGDB_FILE           "adplug.db"
68
69 // Default AdPlug user's configuration subdirectory
70 #define ADPLUG_CONFDIR          ".adplug"
71
72 // Default path to AdPlug's system-wide database file
73 #ifdef ADPLUG_DATA_DIR
74 #  define ADPLUGDB_PATH         ADPLUG_DATA_DIR "/" ADPLUGDB_FILE
75 #else
76 #  define ADPLUGDB_PATH         ADPLUGDB_FILE
77 #endif
78
79 // Unknown filetype indicator
80 #define UNKNOWN_FILETYPE        "*** Unknown ***"
81
82 // Message urgency levels
83 #define MSG_PANIC       0       // Unmaskable
84 #define MSG_ERROR       1
85 #define MSG_WARN        2
86 #define MSG_NOTE        3
87 #define MSG_DEBUG       4
88
89 /***** Global variables *****/
90
91 static const struct {
92   const char                            *typestr;
93   CAdPlugDatabase::CRecord::RecordType  type;
94 } rtypes[] = {
95   { "plain", CAdPlugDatabase::CRecord::Plain },
96   { "songinfo", CAdPlugDatabase::CRecord::SongInfo },
97   { "clockspeed", CAdPlugDatabase::CRecord::ClockSpeed },
98   {0}
99 };
100
101 static struct {
102   char                          *db_file;
103   CAdPlugDatabase::CRecord::RecordType  rtype;
104   int                                   message_level;
105   bool                                  usedefaultdb, usercomment, cmdkeys;
106   const char                            *homedir;
107 } cfg = {
108   ADPLUGDB_PATH,
109   CAdPlugDatabase::CRecord::Plain,
110   MSG_NOTE,
111   false, false, false,
112   NULL
113 };
114
115 static CAdPlugDatabase  mydb;
116 static const char       *program_name;
117
118 /***** Functions *****/
119
120 static void message(int level, const char *fmt, ...)
121 {
122   va_list argptr;
123
124   if(cfg.message_level < level) return;
125
126   fprintf(stderr, "%s: ", program_name);
127   va_start(argptr, fmt);
128   vfprintf(stderr, fmt, argptr);
129   va_end(argptr);
130   fprintf(stderr, "\n");
131 }
132
133 static CAdPlugDatabase::CKey file2key(const char *filename)
134 {
135   if(cfg.cmdkeys) {     // We got a key spec
136     CAdPlugDatabase::CKey key;
137
138     sscanf(filename, "%hx:%lx", &key.crc16, &key.crc32);
139     return key;
140   } else {              // We got a real filename
141     binifstream f(filename);
142
143     if(f.error()) {
144       message(MSG_ERROR, "can't open specified file -- %s", filename);
145       exit(EXIT_FAILURE);
146     }
147
148     return CAdPlugDatabase::CKey(f);
149   }
150 }
151
152 static void usage()
153 {
154   printf("Usage: %s [options] <command> [arguments]\n\n"
155          "Commands:\n"
156          "  add <files>      Add (and replace) files to database\n"
157          "  list [files]     List files (or everything) from database\n"
158          "  remove <files>   Remove files from database\n"
159          "  merge <files>    Merge other databases with the current one\n"
160          "\n"
161          "Database options:\n"
162          "  -d <file>        Use different database file\n"
163 #ifdef ADPLUG_DATA_DIR
164          "  -s               Use system-wide database file (" ADPLUGDB_PATH ")\n"
165 #endif
166          "  -t <type>        Add as different record type (plain, songinfo, clockspeed)\n"
167          "  -c               Prompt for record comment\n"
168          "  -k               Specify keys instead of files on commandline\n"
169          "\n"
170          "Generic options:\n"
171          "  -q               Be more quiet\n"
172          "  -v               Be more verbose\n"
173          "  -h               Display this help\n"
174          "  -V               Display version information\n",
175          program_name);
176 }
177
178 static const std::string file2type(const char *filename)
179 {
180   CPlayers::const_iterator      i;
181   CSilentopl                    opl;
182   CPlayer                       *p;
183
184   for(i = CAdPlug::players.begin(); i != CAdPlug::players.end(); i++)
185     if((p = (*i)->factory(&opl)))
186       if(p->load(filename)) {
187         delete p;
188         return (*i)->filetype;
189       } else
190         delete p;
191
192   message(MSG_WARN, "unknown filetype -- %s", filename);
193   return UNKNOWN_FILETYPE;
194 }
195
196 static void db_add(const char *filename)
197 {
198   CAdPlugDatabase::CRecord *record = CAdPlugDatabase::CRecord::factory(cfg.rtype);
199
200   if(cfg.cmdkeys) {
201     message(MSG_ERROR, "adding records is only possible while specifying"
202             "filenames");
203     exit(EXIT_FAILURE);
204   }
205
206   if(!record) {
207     message(MSG_PANIC, "internal error (not enough memory?)");
208     exit(EXIT_FAILURE);
209   }
210
211   record->key = file2key(filename);
212   record->filetype = file2type(filename);
213   if(cfg.usercomment)
214     std::cin >> record->comment;
215   else
216     record->comment = filename;
217   if(record->filetype == UNKNOWN_FILETYPE) { delete record; return; }
218   if(!record->user_read(std::cin, std::cout)) {
219     message(MSG_ERROR, "data entry error");
220     exit(EXIT_FAILURE);
221   }
222
223   if(mydb.lookup(record->key)) {
224     message(MSG_NOTE, "replacing previous record -- %s", filename);
225     mydb.wipe();
226   }
227
228   if(mydb.insert(record)) {
229     message(MSG_NOTE, "added record, file type \"%s\" -- %s",
230             record->filetype.c_str(), filename);
231   } else {
232     delete record;
233     message(MSG_ERROR, "error adding record to database -- %s", filename);
234     exit(EXIT_FAILURE);
235   }
236 }
237
238 static bool db_resolve(const char *filename)
239 /* Resolves and lists one entry from the database */
240 {
241   if(mydb.lookup(file2key(filename))) {
242     message(MSG_NOTE, "viewing entry -- %s", filename);
243     mydb.get_record()->user_write(std::cout);
244     return true;
245   } else {
246     message(MSG_WARN, "no entry in database -- %s", filename);
247     return false;
248   }
249 }
250
251 static void db_remove(const char *filename)
252 /* Removes one entry from the database */
253 {
254   if(mydb.lookup(file2key(filename))) {
255     mydb.wipe();
256     message(MSG_NOTE, "deleted entry -- %s", filename);
257   } else
258     message(MSG_WARN, "no entry in database, could not delete -- %s", filename);
259 }
260
261 static void copyright()
262 /* Print copyright notice and version information */
263 {
264   printf("AdPlug database maintenance utility %s\n", CAdPlug::get_version().c_str());
265   printf("Copyright (c) 2002 Riven the Mage <riven@ok.ru>\n"
266          "Copyright (c) 2002, 2003 Simon Peter <dn.tlp@gmx.net>\n");
267 }
268
269 static void db_error(bool dbokay)
270 /* Checks if database is open. Exits program otherwise */
271 {
272   if(!dbokay) { // Database could not be opened
273     message(MSG_ERROR, "database could not be opened -- %s", cfg.db_file);
274     exit(EXIT_FAILURE);
275   }
276 }
277
278 static void db_save(void)
279 /* Saves database to file, making path if it doesn't exist yet. */
280 {
281 #if HAVE_MKDIR
282   std::string savedir;
283 #endif
284
285   if(!mydb.save(cfg.db_file)) {
286 #if HAVE_MKDIR
287     if(cfg.homedir) {
288       savedir = cfg.homedir; savedir += "/" ADPLUG_CONFDIR;
289       mkdir(savedir.c_str(), 0755);
290       if(mydb.save(cfg.db_file)) return;
291     }
292 #endif
293     message(MSG_ERROR, "could not save database -- %s", cfg.db_file);
294   }
295 }
296
297 static void shutdown(void)
298 {
299   // Free userdb variable, if applicable
300   if(cfg.homedir && !cfg.usedefaultdb) free(cfg.db_file);
301 }
302
303 /***** Main program *****/
304
305 int main(int argc, char *argv[])
306 {
307   char          opt;
308   bool          dbokay;
309   unsigned int  i;
310
311   // Init
312   program_name = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 :
313         (strrchr(argv[0], '\\') ? strrchr(argv[0], '\\') + 1 : argv[0]);
314   atexit(shutdown);
315
316   // Parse options
317   while((opt = getopt(argc, argv, "d:t:qvhVsck")) != -1)
318     switch(opt) {
319     case 'd': cfg.db_file = optarg; break;              // Set database file
320     case 't': // Different record type
321       for(i = 0; rtypes[i].typestr; i++)
322         if(!strcmp(rtypes[i].typestr, optarg)) {
323           cfg.rtype = rtypes[i].type;
324           break;
325         }
326
327       if(!rtypes[i].typestr) {
328         message(MSG_ERROR, "unknown record type -- %s", optarg);
329         exit(EXIT_FAILURE);
330       }
331       break;
332     case 'q': if(cfg.message_level) cfg.message_level--; break; // Be more quiet
333     case 'v': cfg.message_level++; break;               // Be more verbose
334     case 'h': usage(); exit(EXIT_SUCCESS); break;       // Display help
335     case 'V': copyright(); exit(EXIT_SUCCESS); break;   // Display version
336     case 's':                                           // Use system-wide database
337 #ifdef ADPLUG_DATA_DIR
338       cfg.usedefaultdb = true;
339 #else
340       message(MSG_WARN, "option not supported on this system -- s");
341 #endif
342       break;
343     case 'c': cfg.usercomment = true; break;            // Prompt for comments
344     case 'k': cfg.cmdkeys = true; break;                // Keys on commandline
345     case '?': exit(EXIT_FAILURE);
346     }
347
348   // Check for commands
349   if(argc == optind) {
350     fprintf(stderr, "%s: need a command\n", program_name);
351     fprintf(stderr, "Try '%s -h' for more information.\n", program_name);
352     exit(EXIT_FAILURE);
353   }
354
355   // Try user's home directory first, before trying the default location.
356   cfg.homedir = getenv("HOME");
357   if(cfg.homedir && !cfg.usedefaultdb) {
358     cfg.db_file = (char *)malloc(strlen(cfg.homedir) + strlen(ADPLUG_CONFDIR) +
359                                                                  strlen(ADPLUGDB_FILE) + 3);
360     strcpy(cfg.db_file, cfg.homedir);
361         strcat(cfg.db_file, "/" ADPLUG_CONFDIR "/");
362         strcat(cfg.db_file, ADPLUGDB_FILE);
363   }
364
365   // Load database file
366   message(MSG_DEBUG, "using database -- %s", cfg.db_file);
367   dbokay = mydb.load(cfg.db_file);
368
369   // Parse commands
370   if(!strcmp(argv[optind], "add")) {    // Add file to database
371     if(++optind < argc) {
372       for(;optind < argc; optind++)
373         db_add(argv[optind]);
374       db_save();
375     } else {
376       message(MSG_ERROR, "add -- missing file argument");
377       exit(EXIT_FAILURE);
378     }
379   } else
380   if(!strcmp(argv[optind], "list")) {   // List (files from) database
381     db_error(dbokay);
382     if(++optind < argc) {
383       for(;optind < argc; optind++) {
384         db_resolve(argv[optind]);
385         printf("\n");
386       }
387     } else {
388       mydb.goto_begin();
389       do {
390         mydb.get_record()->user_write(std::cout);
391         printf("\n");
392       } while(mydb.go_forward());
393     }
394   } else
395   if(!strcmp(argv[optind], "remove")) { // Remove files from database
396     db_error(dbokay);
397     if(++optind < argc) {
398       for(;optind < argc; optind++)
399         db_remove(argv[optind]);
400       db_save();
401     } else {
402       message(MSG_ERROR, "remove -- missing file argument");
403       exit(EXIT_FAILURE);
404     }
405   } else
406   if(!strcmp(argv[optind], "merge")) {  // Merge databases together
407     db_error(dbokay);
408     if(++optind < argc) {
409       for(;optind < argc; optind++)
410         if(mydb.load(argv[optind]))
411           message(MSG_NOTE, "merged database -- %s", argv[optind]);
412         else
413           message(MSG_WARN, "could not open database -- %s", argv[optind]);
414       db_save();
415     } else {
416       message(MSG_ERROR, "merge -- missing file argument");
417       exit(EXIT_FAILURE);
418     }
419   } else {
420     message(MSG_ERROR, "unknown command -- %s", argv[optind]);
421     exit(EXIT_FAILURE);
422   }
423
424   return EXIT_SUCCESS;
425 }