A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2005 by Miika Pekkarinen
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22/*
23 * TagCache API
24 *
25 * ----------x---------x------------------x-----
26 * | | | External
27 * +---------------x-------+ | TagCache | Libraries
28 * | Modification routines | | Core |
29 * +-x---------x-----------+ | |
30 * | (R/W) | | | |
31 * | +------x-------------x-+ +-------------x-----+ |
32 * | | x==x Filters & clauses | |
33 * | | Search routines | +-------------------+ |
34 * | | x============================x DirCache
35 * | +-x--------------------+ | (optional)
36 * | | (R) |
37 * | | +-------------------------------+ +---------+ |
38 * | | | DB Commit (sort,unique,index) | | | |
39 * | | +-x--------------------------x--+ | Control | |
40 * | | | (R/W) | (R) | Thread | |
41 * | | | +----------------------+ | | | |
42 * | | | | TagCache DB Builder | | +---------+ |
43 * | | | +-x-------------x------+ | |
44 * | | | | (R) | (W) | |
45 * | | | | +--x--------x---------+ |
46 * | | | | | Temporary Commit DB | |
47 * | | | | +---------------------+ |
48 * +-x----x-------x--+ |
49 * | TagCache RAM DB x==\(W) +-----------------+ |
50 * +-----------------+ \===x | |
51 * | | | | (R) | Ram DB Loader x============x DirCache
52 * +-x----x---x---x---+ /==x | | (optional)
53 * | Tagcache Disk DB x==/ +-----------------+ |
54 * +------------------+ |
55 *
56 */
57
58#if !defined(PLUGIN)
59
60/*#define LOGF_ENABLE*/
61/*#define LOGF_CLAUSES define to enable logf clause matching (LOGF_ENABLE req'd) */
62
63#include <stdio.h>
64#include <stdlib.h>
65#include <ctype.h>
66#ifdef APPLICATION
67#include <unistd.h> /* readlink() */
68#include <limits.h> /* PATH_MAX */
69#endif
70#include "config.h"
71#include "ata_idle_notify.h"
72#include "thread.h"
73#include "kernel.h"
74#include "system.h"
75#include "logf.h"
76#include "string-extra.h"
77#include "usb.h"
78#include "metadata.h"
79#include "tagcache.h"
80#include "yesno.h"
81#include "core_alloc.h"
82#include "crc32.h"
83#include "misc.h"
84#include "settings.h"
85#include "dir.h"
86#include "pathfuncs.h"
87#include "debug.h"
88#include "dircache.h"
89#include "errno.h"
90
91#ifndef __PCTOOL__
92#include "lang.h"
93#include "eeprom_settings.h"
94#endif
95#define USR_CANCEL false
96#else/*!defined(PLUGIN)*/
97#define USR_CANCEL (tc_stat.commit_delayed == true)
98#endif /*!defined(PLUGIN)*/
99/*
100 * Define this to support non-native endian tagcache files.
101 * Databases are always written in native endian so this is
102 * basically only necessary to support databases generated
103 * by the PC database tool.
104 *
105 * Adds around 0.5-1.0k of code.
106 */
107#define TAGCACHE_SUPPORT_FOREIGN_ENDIAN
108
109/* Allow a little drift to the filename ordering (should not be too high/low). */
110#define POS_HISTORY_COUNT 4
111
112/* How much to pre-load entries while committing to prevent seeking. */
113#define IDX_BUF_DEPTH 64
114
115/* Tag Cache Header version 'TCHxx'. Increment when changing internal structures. */
116#define TAGCACHE_MAGIC 0x54434810
117
118/* Dump store/restore header version 'TCSxx'. */
119#define TAGCACHE_STATEFILE_MAGIC 0x54435301
120
121/* How much to allocate extra space for ramcache. */
122#define TAGCACHE_RESERVE 32768
123
124/*
125 * Define how long one entry must be at least (longer -> less memory at commit).
126 * Must be at least 4 bytes in length for correct alignment.
127 */
128#define TAGFILE_ENTRY_CHUNK_LENGTH 8
129
130/* Used to guess the necessary buffer size at commit. */
131#define TAGFILE_ENTRY_AVG_LENGTH 16
132
133/* Max events in the internal tagcache command queue. */
134#define TAGCACHE_COMMAND_QUEUE_LENGTH 32
135
136/* Idle time before committing events in the command queue. */
137#define TAGCACHE_COMMAND_QUEUE_COMMIT_DELAY HZ*2
138
139/* Dont commit database_tmp data. */
140#define TAGCACHE_FILE_NOCOMMIT "database_commit.ignore"
141
142/* Temporary database containing new tags to be committed to the main db. */
143#define TAGCACHE_FILE_TEMP "database_tmp.tcd"
144
145/* The main database master index and numeric data. */
146#define TAGCACHE_FILE_MASTER "database_idx.tcd"
147
148/* The main database string data. */
149#define TAGCACHE_FILE_INDEX "database_%d.tcd"
150
151/* ASCII dumpfile of the DB contents. */
152#define TAGCACHE_FILE_CHANGELOG "database_changelog.txt"
153
154/* Serialized DB. */
155#define TAGCACHE_STATEFILE "database_state.tcd"
156
157/* Flags */
158#define FLAG_DELETED 0x0001 /* Entry has been removed from db */
159#define FLAG_DIRCACHE 0x0002 /* Filename is a dircache pointer */
160#define FLAG_DIRTYNUM 0x0004 /* Numeric data has been modified */
161#define FLAG_TRKNUMGEN 0x0008 /* Track number has been generated */
162#define FLAG_RESURRECTED 0x0010 /* Statistics data has been resurrected */
163
164#ifdef __PCTOOL__
165#define yield() do { } while(0)
166#define sim_sleep(timeout) do { } while(0)
167#define do_timed_yield() do { } while(0)
168static void db_log(const char *prefix, const char *msg);
169#endif
170
171#ifdef __PCTOOL__
172#define DB_LOG(__prefix, __file) db_log(__prefix, __file)
173#else
174#define DB_LOG(...)
175#endif
176
177#ifndef __PCTOOL__
178/* Tag Cache thread. */
179static struct event_queue tagcache_queue SHAREDBSS_ATTR;
180static long tagcache_stack[(DEFAULT_STACK_SIZE + 0x4000)/sizeof(long)];
181static const char tagcache_thread_name[] = "tagcache";
182#endif
183
184/* Previous path when scanning directory tree recursively. */
185static char curpath[TAGCACHE_BUFSZ];
186/* Shared buffer for several build_index fns to reduce stack usage */
187static char build_idx_buf[TAGCACHE_BUFSZ];
188static const long build_idx_bufsz = sizeof(build_idx_buf);
189/* Used when removing duplicates. */
190static char *tempbuf; /* Allocated when needed. */
191static long tempbufidx; /* Current location in buffer. */
192static size_t tempbuf_size; /* Buffer size (TEMPBUF_SIZE). */
193static long tempbuf_left; /* Buffer space left. */
194static long tempbuf_pos;
195#ifndef __PCTOOL__
196static int tempbuf_handle;
197#endif
198
199#define SORTED_TAGS_COUNT 9
200#define TAGCACHE_IS_UNIQUE(tag) (BIT_N(tag) & TAGCACHE_UNIQUE_TAGS)
201#define TAGCACHE_IS_SORTED(tag) (BIT_N(tag) & TAGCACHE_SORTED_TAGS)
202#define TAGCACHE_IS_NUMERIC_OR_NONUNIQUE(tag) \
203 (BIT_N(tag) & (TAGCACHE_NUMERIC_TAGS | ~TAGCACHE_UNIQUE_TAGS))
204/* Tags we want to get sorted (loaded to the tempbuf). */
205#define TAGCACHE_SORTED_TAGS ((1LU << tag_artist) | (1LU << tag_album) | \
206 (1LU << tag_genre) | (1LU << tag_composer) | (1LU << tag_comment) | \
207 (1LU << tag_albumartist) | (1LU << tag_grouping) | (1LU << tag_title) | \
208 (1LU << tag_virt_canonicalartist))
209
210/* Uniqued tags (we can use these tags with filters and conditional clauses). */
211#define TAGCACHE_UNIQUE_TAGS ((1LU << tag_artist) | (1LU << tag_album) | \
212 (1LU << tag_genre) | (1LU << tag_composer) | (1LU << tag_comment) | \
213 (1LU << tag_albumartist) | (1LU << tag_grouping) | \
214 (1LU << tag_virt_canonicalartist))
215
216/* String presentation of the tags defined in tagcache.h. Must be in correct order! */
217static const char * const tags_str[] = { "artist", "album", "genre", "title",
218 "filename", "composer", "comment", "albumartist", "grouping", "year",
219 "discnumber", "tracknumber", "canonicalartist", "bitrate", "length",
220 "playcount", "rating", "playtime", "lastplayed", "commitid", "mtime",
221 "lastelapsed", "lastoffset"
222#if !defined(LOGF_ENABLE) || !defined(LOGF_CLAUSES)
223};
224#define logf_clauses(...) do { } while(0)
225#else /* strings for logf debugging */
226 "tag_virt_basename", "tag_virt_length_min", "tag_virt_length_sec",
227 "tag_virt_playtime_min", "tag_virt_playtime_sec",
228 "tag_virt_entryage", "tag_virt_autoscore"
229};
230/* more debug strings */
231static const char * const tag_type_str[] = {
232 [clause_none] = "clause_none", [clause_is] = "clause_is",
233 [clause_is_not] = "clause_is_not", [clause_gt] = "clause_gt",
234 [clause_gteq] = "clause_gteq", [clause_lt] = "clause_lt",
235 [clause_lteq] = "clause_lteq", [clause_contains] = "clause_contains",
236 [clause_not_contains] = "clause_not_contains",
237 [clause_begins_with] = "clause_begins_with",
238 [clause_not_begins_with] = "clause_not_begins_with",
239 [clause_ends_with] = "clause_ends_with",
240 [clause_not_ends_with] = "clause_not_ends_with",
241 [clause_oneof] = "clause_oneof",
242 [clause_begins_oneof] = "clause_begins_oneof",
243 [clause_ends_oneof] = "clause_ends_oneof",
244 [clause_not_oneof] = "clause_not_oneof",
245 [clause_not_begins_oneof] = "clause_not_begins_oneof",
246 [clause_not_ends_oneof] = "clause_not_ends_oneof",
247 [clause_logical_or] = "clause_logical_or"
248 };
249#define logf_clauses logf
250#endif /* !defined(LOGF_ENABLE) || !defined(LOGF_CLAUSES) */
251
252#if defined(PLUGIN)
253char *itoa_buf(char *buf, size_t bufsz, long int i)
254{
255 snprintf(buf, bufsz, "%ld", i);
256 return buf;
257}
258#endif
259
260/* Status information of the tagcache. */
261static struct tagcache_stat tc_stat;
262
263/* Queue commands. */
264enum tagcache_queue {
265 Q_STOP_SCAN = 0,
266 Q_START_SCAN,
267 Q_IMPORT_CHANGELOG,
268 Q_UPDATE,
269 Q_REBUILD,
270
271 /* Internal tagcache command queue. */
272 CMD_UPDATE_MASTER_HEADER,
273 CMD_UPDATE_NUMERIC,
274};
275
276struct tagcache_command_entry {
277 int32_t command;
278 int32_t idx_id;
279 int32_t tag;
280 int32_t data;
281};
282
283#ifndef __PCTOOL__
284static struct tagcache_command_entry command_queue[TAGCACHE_COMMAND_QUEUE_LENGTH];
285static volatile int command_queue_widx = 0;
286static volatile int command_queue_ridx = 0;
287static struct mutex command_queue_mutex SHAREDBSS_ATTR;
288#endif
289
290/* Tag database structures. */
291
292/* Variable-length tag entry in tag files. */
293struct tagfile_entry {
294 int32_t tag_length; /* Length of the data in bytes including '\0' */
295 int32_t idx_id; /* Corresponding entry location in index file of not unique tags */
296 char tag_data[0]; /* Begin of the tag data */
297};
298
299/* Fixed-size tag entry in master db index. */
300struct index_entry {
301 int32_t tag_seek[TAG_COUNT]; /* Location of tag data or numeric tag data */
302 int32_t flag; /* Status flags */
303};
304
305/* Header is the same in every file. */
306struct tagcache_header {
307 int32_t magic; /* Header version number */
308 int32_t datasize; /* Data size in bytes */
309 int32_t entry_count; /* Number of entries in this file */
310};
311
312struct master_header {
313 struct tagcache_header tch;
314 int32_t serial; /* Increasing counting number */
315 int32_t commitid; /* Number of commits so far */
316 int32_t dirty;
317};
318
319static struct master_header current_tcmh;
320
321#ifdef HAVE_TC_RAMCACHE
322
323#define TC_ALIGN_PTR(p, type, gap_out_p) \
324 ({ typeof (p) __p = (p); \
325 typeof (p) __palgn = ALIGN_UP(__p, __alignof__(type)); \
326 *(gap_out_p) = (char *)__palgn - (char *)__p; \
327 __palgn; })
328
329#define IF_TCRCDC(...) IF_DIRCACHE(__VA_ARGS__)
330
331#ifdef HAVE_DIRCACHE
332#define tcrc_dcfrefs \
333 ((struct dircache_fileref *)(tcramcache.hdr->tags[tag_filename] + \
334 sizeof (struct tagcache_header)))
335#endif /* HAVE_DIRCACHE */
336
337/* Header is created when loading database to ram. */
338struct ramcache_header {
339 char *tags[TAG_COUNT]; /* Tag file content (dcfrefs if tag_filename) */
340 int entry_count[TAG_COUNT]; /* Number of entries in the indices. */
341 struct index_entry indices[0]; /* Master index file content */
342};
343
344#ifdef HAVE_EEPROM_SETTINGS
345struct statefile_header {
346 int32_t magic; /* Statefile version number */
347 struct master_header mh; /* Header from the master index */
348 struct ramcache_header *hdr; /* Old load address of hdr for relocation */
349 struct tagcache_stat tc_stat;
350};
351#endif /* HAVE_EEPROM_SETTINGS */
352
353/* In-RAM ramcache structure (not persisted) */
354static struct tcramcache
355{
356 struct ramcache_header *hdr; /* allocated ramcache_header */
357 int handle; /* buffer handle */
358} tcramcache;
359
360static inline void tcrc_buffer_lock(void)
361{
362 core_pin(tcramcache.handle);
363}
364
365static inline void tcrc_buffer_unlock(void)
366{
367 core_unpin(tcramcache.handle);
368}
369
370#else /* ndef HAVE_TC_RAMCACHE */
371
372#define IF_TCRCDC(...)
373
374#endif /* HAVE_TC_RAMCACHE */
375
376/**
377 * Full tag entries stored in a temporary file waiting
378 * for commit to the cache. */
379struct temp_file_entry {
380 int32_t tag_offset[TAG_COUNT];
381 int16_t tag_length[TAG_COUNT];
382 int32_t flag;
383 int32_t data_length;
384};
385
386struct tempbuf_id_list {
387 long id;
388 struct tempbuf_id_list *next;
389};
390
391struct tempbuf_searchidx {
392 long idx_id;
393 char *str;
394 int seek;
395 struct tempbuf_id_list idlist;
396};
397
398/* Lookup buffer for fixing messed up index while after sorting. */
399static long commit_entry_count;
400static long lookup_buffer_depth;
401static struct tempbuf_searchidx **lookup;
402
403/* Used when building the temporary file. */
404static int cachefd = -1, filenametag_fd;
405static int total_entry_count = 0;
406static int data_size = 0;
407static int processed_dir_count;
408
409/* Thread safe locking */
410static volatile int write_lock;
411static volatile int read_lock;
412
413static bool delete_entry(long idx_id);
414
415static inline void str_setlen(char *buf, size_t len)
416{
417 buf[len] = '\0';
418}
419
420const char* tagcache_tag_to_str(int tag)
421{
422 return tags_str[tag];
423}
424
425#ifdef TAGCACHE_SUPPORT_FOREIGN_ENDIAN
426static void swap_tagfile_entry(struct tagfile_entry *buf)
427{
428 if (tc_stat.econ)
429 {
430 buf->tag_length = swap32(buf->tag_length);
431 buf->idx_id = swap32(buf->idx_id);
432 }
433}
434
435static void swap_index_entry(struct index_entry *buf)
436{
437 if (tc_stat.econ)
438 {
439 for (int i = 0; i < TAG_COUNT; ++i)
440 buf->tag_seek[i] = swap32(buf->tag_seek[i]);
441 buf->flag = swap32(buf->flag);
442 }
443}
444
445static void swap_tagcache_header(struct tagcache_header *buf)
446{
447 if (tc_stat.econ)
448 {
449 buf->magic = swap32(buf->magic);
450 buf->datasize = swap32(buf->datasize);
451 buf->entry_count = swap32(buf->entry_count);
452 }
453}
454
455static void swap_master_header(struct master_header *buf)
456{
457 if (tc_stat.econ)
458 {
459 swap_tagcache_header(&buf->tch);
460 buf->serial = swap32(buf->serial);
461 buf->commitid = swap32(buf->commitid);
462 buf->dirty = swap32(buf->dirty);
463 }
464}
465#else
466static void swap_tagfile_entry(struct tagfile_entry *buf) { (void)buf; }
467static void swap_index_entry(struct index_entry *buf) { (void)buf; }
468static void swap_tagcache_header(struct tagcache_header *buf) { (void)buf; }
469static void swap_master_header(struct master_header *buf) { (void)buf; }
470#endif
471
472static ssize_t read_tagfile_entry(int fd, struct tagfile_entry *buf)
473{
474 ssize_t ret = read(fd, buf, sizeof(*buf));
475 if (ret == sizeof(*buf) && tc_stat.econ)
476 swap_tagfile_entry(buf);
477
478 return ret;
479}
480
481static ssize_t write_tagfile_entry(int fd, struct tagfile_entry *buf)
482{
483 struct tagfile_entry e = *buf;
484
485 swap_tagfile_entry(&e);
486
487 return write(fd, &e, sizeof(e));
488}
489
490enum e_read_errors {
491 e_SUCCESS = 0,
492 e_SUCCESS_LEN_ZERO = 1,
493 e_ENTRY_SIZEMISMATCH,
494 e_TAG_TOOLONG,
495 e_TAG_SIZEMISMATCH
496};
497
498static enum e_read_errors
499read_tagfile_entry_and_tag(int fd, struct tagfile_entry *tfe,
500 char* buf, int bufsz)
501{
502 if (read_tagfile_entry(fd, tfe) != sizeof(struct tagfile_entry))
503 return e_ENTRY_SIZEMISMATCH;
504
505 long tag_length = tfe->tag_length;
506 if (tag_length >= bufsz)
507 return e_TAG_TOOLONG;
508
509 if (tag_length > 0 && read(fd, buf, tag_length) != tag_length)
510 return e_TAG_SIZEMISMATCH;
511
512 str_setlen(buf, tag_length);
513 return (tag_length > 0 && *buf) ? e_SUCCESS : e_SUCCESS_LEN_ZERO;
514}
515
516static ssize_t read_index_entries(int fd, struct index_entry *buf, size_t count)
517{
518 ssize_t ret = read(fd, buf, sizeof(*buf) * count);
519 for (ssize_t i = 0; i < ret; i += sizeof(*buf))
520 swap_index_entry(buf++);
521
522 return ret;
523}
524
525static ssize_t write_index_entries(int fd, struct index_entry *buf, size_t count)
526{
527#ifdef TAGCACHE_SUPPORT_FOREIGN_ENDIAN
528 ssize_t ret = 0;
529 for (; count > 0; count--)
530 {
531 struct index_entry e = *buf++;
532 swap_index_entry(&e);
533
534 ssize_t rc = write(fd, &e, sizeof(e));
535 if (rc < 0)
536 return rc;
537 ret += rc;
538 }
539
540 return ret;
541#else
542 return write(fd, buf, sizeof(*buf) * count);
543#endif
544}
545
546static ssize_t read_tagcache_header(int fd, struct tagcache_header *buf)
547{
548 ssize_t ret = read(fd, buf, sizeof(*buf));
549 if (ret == sizeof(*buf))
550 swap_tagcache_header(buf);
551
552 return ret;
553}
554
555static ssize_t write_tagcache_header(int fd, struct tagcache_header *buf)
556{
557 struct tagcache_header e = *buf;
558 swap_tagcache_header(&e);
559 return write(fd, &e, sizeof(e));
560}
561
562static ssize_t read_master_header(int fd, struct master_header *buf)
563{
564 ssize_t ret = read(fd, buf, sizeof(*buf));
565 if (ret == sizeof(*buf))
566 swap_master_header(buf);
567
568 return ret;
569}
570
571static ssize_t write_master_header(int fd, struct master_header *buf)
572{
573 struct master_header e = *buf;
574 swap_master_header(&e);
575 return write(fd, &e, sizeof(e));
576}
577
578/*
579 * open_db_fd and remove_db_file are noinline to minimize stack usage
580 */
581static int NO_INLINE open_db_fd(const char* filename, int mode)
582{
583 char buf[MAX_PATH];
584
585 if(mode & O_CREAT)
586 {
587 if (mkdir(tc_stat.db_path) < 0 && errno != EEXIST)
588 return -1;
589 }
590
591 return open_pathfmt(buf, sizeof(buf), mode, "%s/%s",
592 tc_stat.db_path, filename);
593}
594
595static int NO_INLINE remove_db_file(const char* filename)
596{
597 char buf[MAX_PATH];
598
599 snprintf(buf, sizeof(buf), "%s/%s",
600 tc_stat.db_path, filename);
601
602 return remove(buf);
603}
604
605static int open_tag_fd(struct tagcache_header *hdr, int tag, bool write)
606{
607 int fd;
608 char fname[MAX_PATH];
609
610 if (TAGCACHE_IS_NUMERIC(tag) || tag < 0 || tag >= TAG_COUNT)
611 return -1;
612
613 fd = open_pathfmt(fname, sizeof(fname),
614 write ? O_RDWR : O_RDONLY, "%s/" TAGCACHE_FILE_INDEX,
615 tc_stat.db_path, tag);
616 if (fd < 0)
617 {
618 logf("%s failed: tag=%d write=%d file= " TAGCACHE_FILE_INDEX,
619 __func__, tag, write, tag);
620 tc_stat.ready = false;
621 return fd;
622 }
623
624 /* Check the header. */
625 if (read_tagcache_header(fd, hdr) != sizeof(struct tagcache_header) ||
626 hdr->magic != TAGCACHE_MAGIC)
627 {
628 logf("header error");
629 tc_stat.ready = false;
630 close(fd);
631 return -2;
632 }
633
634 return fd;
635}
636
637static int open_master_fd(struct master_header *hdr, bool write)
638{
639 int fd;
640 int rc;
641
642 fd = open_db_fd(TAGCACHE_FILE_MASTER, write ? O_RDWR : O_RDONLY);
643 if (fd < 0)
644 {
645 logf("master file open failed for R/W");
646 tc_stat.ready = false;
647 return fd;
648 }
649
650 rc = read(fd, hdr, sizeof(struct master_header));
651 if (rc != sizeof(struct master_header))
652 {
653 logf("master file read failed");
654 close(fd);
655 return -1;
656 }
657
658 /* Tagcache files can have either endianness. A device will always
659 * create files in its native endianness, but we accept non-native
660 * endian files for compatibility reasons. */
661 if (hdr->tch.magic == TAGCACHE_MAGIC)
662 tc_stat.econ = false;
663#ifdef TAGCACHE_SUPPORT_FOREIGN_ENDIAN
664 else if (hdr->tch.magic == swap32(TAGCACHE_MAGIC))
665 {
666 tc_stat.econ = true;
667 swap_master_header(hdr);
668 }
669#endif
670 else
671 {
672 logf("master file bad magic: %08lx\n", (unsigned long)hdr->tch.magic);
673 close(fd);
674 return -2;
675 }
676
677 return fd;
678}
679
680static void remove_files(void)
681{
682 int i;
683 char buf[MAX_PATH];
684 const int bufsz = sizeof(buf);
685 logf("%s", __func__);
686
687 tc_stat.ready = false;
688 tc_stat.ramcache = false;
689 tc_stat.econ = false;
690 remove_db_file(TAGCACHE_FILE_MASTER);
691 for (i = 0; i < TAG_COUNT; i++)
692 {
693 if (TAGCACHE_IS_NUMERIC(i))
694 continue;
695
696 snprintf(buf, bufsz, "%s/" TAGCACHE_FILE_INDEX,
697 tc_stat.db_path, i);
698 remove(buf);
699 }
700}
701
702static bool check_all_headers(void)
703{
704 struct master_header myhdr;
705 struct tagcache_header tch;
706 int tag;
707 int fd;
708
709 if ( (fd = open_master_fd(&myhdr, false)) < 0)
710 return false;
711
712 close(fd);
713 if (myhdr.dirty)
714 {
715 logf("tagcache is dirty!");
716 return false;
717 }
718
719 memcpy(¤t_tcmh, &myhdr, sizeof(struct master_header));
720
721 for (tag = 0; tag < TAG_COUNT; tag++)
722 {
723 if (TAGCACHE_IS_NUMERIC(tag))
724 continue;
725
726 if ( (fd = open_tag_fd(&tch, tag, false)) < 0)
727 return false;
728
729 close(fd);
730 }
731
732 return true;
733}
734
735static bool update_master_header(void)
736{
737 struct master_header myhdr;
738 int fd;
739
740 if (!tc_stat.ready)
741 return false;
742
743 if ( (fd = open_master_fd(&myhdr, true)) < 0)
744 return false;
745
746 myhdr.serial = current_tcmh.serial;
747 myhdr.commitid = current_tcmh.commitid;
748 myhdr.dirty = current_tcmh.dirty;
749
750 /* Write it back */
751 lseek(fd, 0, SEEK_SET);
752 write_master_header(fd, &myhdr);
753 close(fd);
754
755 return true;
756}
757
758#if !defined(PLUGIN)
759#ifndef __PCTOOL__
760static bool do_timed_yield(void)
761{
762 /* Sorting can lock up for quite a while, so yield occasionally */
763 static long wakeup_tick = 0;
764 if (TIME_AFTER(current_tick, wakeup_tick))
765 {
766 yield();
767 wakeup_tick = current_tick + (HZ/25);
768 return true;
769 }
770 return false;
771}
772#endif /* __PCTOOL__ */
773
774static void allocate_tempbuf(void)
775{
776 /* Yeah, malloc would be really nice now :) */
777 size_t size;
778 tempbuf_size = 0;
779
780#ifdef __PCTOOL__
781 size = 32*1024*1024;
782 tempbuf = malloc(size);
783 if (tempbuf)
784 tempbuf_size = size;
785#else /* !__PCTOOL__ */
786 /* Need to pass dummy ops to prevent the buffer being moved
787 * out from under us, since we yield during the tagcache commit. */
788 tempbuf_handle = core_alloc_maximum(&size, &buflib_ops_locked);
789 if (tempbuf_handle > 0)
790 {
791 tempbuf = core_get_data(tempbuf_handle);
792 tempbuf_size = size;
793 }
794#endif /* __PCTOOL__ */
795
796}
797
798static void free_tempbuf(void)
799{
800 if (tempbuf_size == 0)
801 return ;
802
803#ifdef __PCTOOL__
804 free(tempbuf);
805#else
806 tempbuf_handle = core_free(tempbuf_handle);
807#endif
808 tempbuf = NULL;
809 tempbuf_size = 0;
810}
811
812#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
813/* find the ramcache entry corresponding to the file indicated by
814 * filename and dc (it's corresponding dircache id). */
815static long find_entry_ram(const char *filename)
816{
817 static long last_pos = 0;
818 struct dircache_fileref dcfref;
819
820 /* Check if tagcache is loaded into ram. */
821 if (!tc_stat.ramcache
822 || global_settings.tagcache_ram != TAGCACHE_RAM_ON)
823 return -1;
824
825 if (dircache_search(DCS_CACHED_PATH | DCS_UPDATE_FILEREF, &dcfref,
826 filename) <= 0)
827 {
828 logf("tagcache: file not found.");
829 return -1;
830 }
831
832 /* Search references */
833 int end_pos = current_tcmh.tch.entry_count;
834 while (1)
835 {
836 for (int i = last_pos; i < end_pos; i++)
837 {
838 do_timed_yield();
839
840 if (!(tcramcache.hdr->indices[i].flag & FLAG_DIRCACHE))
841 continue;
842
843 int cmp = dircache_fileref_cmp(&tcrc_dcfrefs[i], &dcfref);
844 if (cmp < 3)
845 continue;
846
847 last_pos = MAX(0, i - 3);
848 return i;
849 }
850
851 if (last_pos == 0)
852 {
853 last_pos = MAX(0, end_pos - 3);
854 break;
855 }
856
857 end_pos = last_pos;
858 last_pos = 0;
859 }
860
861 return -1;
862}
863#endif /* defined (HAVE_TC_RAMCACHE) && defined (HAVE_DIRCACHE) */
864
865static long find_entry_disk(const char *filename_raw, bool localfd)
866{
867 struct tagfile_entry tfe;
868 struct tagcache_header tch;
869 static long last_pos = -1;
870 long pos_history[POS_HISTORY_COUNT];
871 unsigned int pos_history_idx = 0;
872 unsigned int i;
873
874 char buf[TAGCACHE_BUFSZ];
875 const long bufsz = sizeof(buf);
876
877 int fd;
878 int pos = -1;
879 long idx = -1;
880
881 bool found = false;
882
883 const char *filename = filename_raw;
884
885#ifdef APPLICATION
886 char pathbuf[PATH_MAX]; /* Note: Don't use MAX_PATH here, it's too small */
887 if (realpath(filename, pathbuf) == pathbuf)
888 filename = pathbuf;
889#endif /* APPLICATION */
890
891 if (!tc_stat.ready)
892 return -2;
893
894 fd = filenametag_fd;
895 if (fd < 0 || localfd)
896 {
897 last_pos = -1;
898 if ( (fd = open_tag_fd(&tch, tag_filename, false)) < 0)
899 return -1;
900 }
901
902 check_again:
903
904 if (last_pos > 0) /* pos gets cached to prevent reading from beginning */
905 pos = lseek(fd, last_pos, SEEK_SET);
906 else /* start back at beginning */
907 pos = lseek(fd, sizeof(struct tagcache_header), SEEK_SET);
908
909 long tag_length = strlen(filename) + 1; /* include NULL */
910
911 if (tag_length < bufsz)
912 {
913 while (true)
914 {
915 for (i = pos_history_idx-1; i < pos_history_idx; i--)
916 pos_history[i+1] = pos_history[i];
917 pos_history[0] = pos;
918
919 if (read_tagfile_entry(fd, &tfe) != sizeof(struct tagfile_entry))
920 {
921 logf("size mismatch find entry");
922 break;
923 }
924 else
925 {
926 pos += sizeof(struct tagfile_entry) + tfe.tag_length;
927 /* don't read the entry unless the length matches */
928 if (tfe.tag_length == tag_length)
929 {
930 if(read(fd, buf, tfe.tag_length) != tag_length)
931 {
932 logf("read error #2");
933 close(fd);
934 if (!localfd)
935 filenametag_fd = -1;
936 last_pos = -1;
937 return -3;
938 }
939 if (!strncmp(filename, buf, tag_length))
940 {
941 last_pos = pos_history[pos_history_idx];
942 found = true;
943 idx = tfe.idx_id;
944 break ;
945 }
946 }
947 else
948 lseek(fd, pos, SEEK_SET);
949
950 }
951 }
952 if (pos_history_idx < POS_HISTORY_COUNT - 1)
953 pos_history_idx++;
954 }
955
956 /* Not found? */
957 if (!found)
958 {
959 if (last_pos > 0) /* start back at the beginning */
960 {
961 last_pos = -1;
962 logf("seek again");
963 goto check_again;
964 }
965
966 idx = -4;
967 }
968
969 if (fd != filenametag_fd || localfd)
970 close(fd);
971
972 return idx;
973}
974
975static int find_index(const char *filename)
976{
977 long idx_id = -1;
978
979#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
980 idx_id = find_entry_ram(filename);
981#endif
982
983 if (idx_id < 0)
984 idx_id = find_entry_disk(filename, true);
985
986 return idx_id;
987}
988
989bool tagcache_find_index(struct tagcache_search *tcs, const char *filename)
990{
991 /* NOTE: on ret==true you need to call tagcache_search_finish(tcs) yourself */
992 int idx_id;
993
994 if (!tc_stat.ready)
995 return false;
996
997 idx_id = find_index(filename);
998 if (idx_id < 0)
999 return false;
1000
1001 if (!tagcache_search(tcs, tag_filename))
1002 return false;
1003
1004 tcs->entry_count = 0;
1005 tcs->idx_id = idx_id;
1006
1007 return true;
1008}
1009
1010static bool get_index(int masterfd, int idxid,
1011 struct index_entry *idx, bool use_ram)
1012{
1013 bool localfd = false;
1014
1015 if (idxid < 0)
1016 {
1017 logf("Incorrect idxid: %d", idxid);
1018 return false;
1019 }
1020
1021#ifdef HAVE_TC_RAMCACHE
1022 if (tc_stat.ramcache && use_ram)
1023 {
1024 if (tcramcache.hdr->indices[idxid].flag & FLAG_DELETED)
1025 return false;
1026
1027 *idx = tcramcache.hdr->indices[idxid];
1028 return true;
1029 }
1030#endif /* HAVE_TC_RAMCACHE */
1031
1032 if (masterfd < 0)
1033 {
1034 struct master_header tcmh;
1035
1036 localfd = true;
1037 masterfd = open_master_fd(&tcmh, false);
1038 if (masterfd < 0)
1039 return false;
1040 }
1041
1042 lseek(masterfd, idxid * sizeof(struct index_entry)
1043 + sizeof(struct master_header), SEEK_SET);
1044 if (read_index_entries(masterfd, idx, 1) != sizeof(struct index_entry))
1045 {
1046 logf("read error #3");
1047 if (localfd)
1048 close(masterfd);
1049
1050 return false;
1051 }
1052
1053 if (localfd)
1054 close(masterfd);
1055
1056 if (idx->flag & FLAG_DELETED)
1057 return false;
1058
1059 return true;
1060
1061 (void)use_ram;
1062}
1063
1064#ifndef __PCTOOL__
1065
1066static bool write_index(int masterfd, int idxid, struct index_entry *idx)
1067{
1068 /* We need to exclude all memory only flags & tags when writing to disk. */
1069 if (idx->flag & FLAG_DIRCACHE)
1070 {
1071 logf("memory only flags!");
1072 return false;
1073 }
1074
1075#ifdef HAVE_TC_RAMCACHE
1076 /* Only update numeric data. Writing the whole index to RAM by memcpy
1077 * destroys dircache pointers!
1078 */
1079 if (tc_stat.ramcache)
1080 {
1081 struct index_entry *idx_ram = &tcramcache.hdr->indices[idxid];
1082
1083 for (int tag = 0; tag < TAG_COUNT; tag++)
1084 {
1085 if (TAGCACHE_IS_NUMERIC(tag))
1086 {
1087 idx_ram->tag_seek[tag] = idx->tag_seek[tag];
1088 }
1089 }
1090
1091 /* Don't touch the dircache flag or attributes. */
1092 idx_ram->flag = (idx->flag & 0x0000ffff)
1093 | (idx_ram->flag & (0xffff0000 | FLAG_DIRCACHE));
1094 }
1095#endif /* HAVE_TC_RAMCACHE */
1096
1097 lseek(masterfd, idxid * sizeof(struct index_entry)
1098 + sizeof(struct master_header), SEEK_SET);
1099 if (write_index_entries(masterfd, idx, 1) != sizeof(struct index_entry))
1100 {
1101 logf("write error #3");
1102 logf("idxid: %d", idxid);
1103 return false;
1104 }
1105
1106 return true;
1107}
1108
1109#endif /* !__PCTOOL__ */
1110
1111static bool open_files(struct tagcache_search *tcs, int tag)
1112{
1113 if (tcs->idxfd[tag] < 0)
1114 {
1115 char fname[MAX_PATH];
1116 tcs->idxfd[tag] = open_pathfmt(fname, sizeof(fname),
1117 O_RDONLY, "%s/" TAGCACHE_FILE_INDEX,
1118 tc_stat.db_path, tag);
1119 if (tcs->idxfd[tag] < 0)
1120 {
1121 logf("File not open!");
1122 return false;
1123 }
1124 }
1125
1126 return true;
1127}
1128
1129static bool retrieve(struct tagcache_search *tcs, IF_DIRCACHE(int idx_id,)
1130 struct index_entry *idx, int tag, char *buf, long bufsz)
1131{
1132 bool success = false;
1133 bool is_basename = false;
1134 struct tagfile_entry tfe;
1135 long seek;
1136
1137 if (tag == tag_virt_basename)
1138 {
1139 tag = tag_filename;
1140 is_basename = true;
1141 }
1142
1143 if (TAGCACHE_IS_NUMERIC(tag))
1144 goto failure;
1145
1146 seek = idx->tag_seek[tag];
1147 if (seek < 0)
1148 {
1149 logf("Retrieve failed");
1150 goto failure;
1151 }
1152
1153#ifdef HAVE_TC_RAMCACHE
1154 if (tcs->ramsearch)
1155 {
1156#ifdef HAVE_DIRCACHE
1157 if (tag == tag_filename && (idx->flag & FLAG_DIRCACHE))
1158 {
1159 if (dircache_get_fileref_path(&tcrc_dcfrefs[idx_id], buf, bufsz) >= 0)
1160 success = true;
1161 }
1162 else
1163#endif /* HAVE_DIRCACHE */
1164 if (tag != tag_filename)
1165 {
1166 struct tagfile_entry *ep =
1167 (struct tagfile_entry *)&tcramcache.hdr->tags[tag][seek];
1168 strmemccpy(buf, ep->tag_data, bufsz);
1169 success = true;
1170 }
1171 }
1172#endif /* HAVE_TC_RAMCACHE */
1173
1174 if (!success && open_files(tcs, tag))
1175 {
1176 lseek(tcs->idxfd[tag], seek, SEEK_SET);
1177 switch (read_tagfile_entry_and_tag(tcs->idxfd[tag], &tfe, buf, bufsz))
1178 {
1179 case e_ENTRY_SIZEMISMATCH:
1180 logf("read error #5");
1181 break;
1182 case e_TAG_TOOLONG:
1183 logf("too long tag #5");
1184 break;
1185 case e_TAG_SIZEMISMATCH:
1186 logf("read error #6");
1187 break;
1188 default:
1189 logf("unknown_error");
1190 break;
1191 case e_SUCCESS_LEN_ZERO:
1192 case e_SUCCESS:
1193 success = true;
1194 break;
1195 }
1196 }
1197
1198 if (success)
1199 {
1200 if (is_basename)
1201 {
1202 char* basename = strrchr(buf, '/');
1203 if (basename != NULL)
1204 memmove(buf, basename + 1, strlen(basename)); /* includes NULL */
1205 }
1206 return true;
1207 }
1208
1209failure:
1210 str_setlen(buf, 0);
1211 return false;
1212}
1213
1214#define COMMAND_QUEUE_IS_EMPTY (command_queue_ridx == command_queue_widx)
1215
1216static long tc_find_tag(int tag, int idx_id, const struct index_entry *idx)
1217{
1218#ifndef __PCTOOL__
1219 if (! COMMAND_QUEUE_IS_EMPTY && TAGCACHE_IS_NUMERIC(tag))
1220 {
1221 /* Attempt to find tag data through store-to-load forwarding in
1222 command queue */
1223 long result = -1;
1224
1225 mutex_lock(&command_queue_mutex);
1226
1227 int ridx = command_queue_widx;
1228
1229 while (ridx != command_queue_ridx)
1230 {
1231 if (--ridx < 0)
1232 ridx = TAGCACHE_COMMAND_QUEUE_LENGTH - 1;
1233
1234 if (command_queue[ridx].command == CMD_UPDATE_NUMERIC
1235 && command_queue[ridx].idx_id == idx_id
1236 && command_queue[ridx].tag == tag)
1237 {
1238 result = command_queue[ridx].data;
1239 break;
1240 }
1241 }
1242
1243 mutex_unlock(&command_queue_mutex);
1244
1245 if (result >= 0)
1246 {
1247 logf("tc_find_tag: Recovered tag %d value %lX from write queue",
1248 tag, (unsigned long) result);
1249 return result;
1250 }
1251 }
1252#else
1253 (void)idx_id;
1254#endif
1255
1256 return idx->tag_seek[tag];
1257}
1258
1259static inline long sec_in_ms(long ms)
1260{
1261 return (ms/1000) % 60;
1262}
1263
1264static inline long min_in_ms(long ms)
1265{
1266 return (ms/1000) / 60;
1267}
1268
1269static long check_virtual_tags(int tag, int idx_id,
1270 const struct index_entry *idx)
1271{
1272 long data = 0;
1273
1274 switch (tag)
1275 {
1276 case tag_virt_length_sec:
1277 data = sec_in_ms(tc_find_tag(tag_length, idx_id, idx));
1278 break;
1279
1280 case tag_virt_length_min:
1281 data = min_in_ms(tc_find_tag(tag_length, idx_id, idx));
1282 break;
1283
1284 case tag_virt_playtime_sec:
1285 data = sec_in_ms(tc_find_tag(tag_playtime, idx_id, idx));
1286 break;
1287
1288 case tag_virt_playtime_min:
1289 data = min_in_ms(tc_find_tag(tag_playtime, idx_id, idx));
1290 break;
1291
1292 case tag_virt_autoscore:
1293 if (tc_find_tag(tag_length, idx_id, idx) == 0
1294 || tc_find_tag(tag_playcount, idx_id, idx) == 0)
1295 {
1296 data = 0;
1297 }
1298 else
1299 {
1300 /* A straight calculus gives:
1301 autoscore = 100 * playtime / length / playcout (1)
1302 Now, consider the euclidian division of playtime by length:
1303 playtime = alpha * length + beta
1304 With:
1305 0 <= beta < length
1306 Now, (1) becomes:
1307 autoscore = 100 * (alpha / playcout + beta / length / playcount)
1308 Both terms should be small enough to avoid any overflow
1309 */
1310 long playtime = tc_find_tag(tag_playtime, idx_id, idx);
1311 long length = tc_find_tag(tag_length, idx_id, idx);
1312 long playcount = tc_find_tag(tag_playcount, idx_id, idx);
1313 data = 100 * (playtime / length) + (100 * (playtime % length)) / length;
1314 data /= playcount;
1315 }
1316 break;
1317
1318 /* How many commits before the file has been added to the DB. */
1319 case tag_virt_entryage:
1320 data = current_tcmh.commitid
1321 - tc_find_tag(tag_commitid, idx_id, idx) - 1;
1322 break;
1323
1324 case tag_virt_basename:
1325 tag = tag_filename; /* return filename; caller handles basename */
1326 /* FALLTHRU */
1327
1328 default:
1329 data = tc_find_tag(tag, idx_id, idx);
1330 }
1331
1332 return data;
1333}
1334
1335long tagcache_get_numeric(const struct tagcache_search *tcs, int tag)
1336{
1337 struct index_entry idx;
1338
1339 if (!tc_stat.ready)
1340 return false;
1341
1342 if (!TAGCACHE_IS_NUMERIC(tag))
1343 return -1;
1344
1345 if (!get_index(tcs->masterfd, tcs->idx_id, &idx, true))
1346 return -2;
1347
1348 return check_virtual_tags(tag, tcs->idx_id, &idx);
1349}
1350
1351inline static bool str_ends_with(const char *str1, const char *str2)
1352{
1353 logf_clauses("%s %s %s", str1, __func__, str2);
1354 int str_len = strlen(str1);
1355 int clause_len = strlen(str2);
1356
1357 if (clause_len > str_len)
1358 return false;
1359
1360 return !strcasecmp(&str1[str_len - clause_len], str2);
1361}
1362
1363inline static bool str_oneof(const char *str, const char *list)
1364{
1365 logf_clauses("%s %s %s", str, __func__, list);
1366 const char *sep;
1367 int l, len = strlen(str);
1368
1369 while (*list)
1370 {
1371 sep = strchr(list, '|');
1372 l = sep ? (intptr_t)sep - (intptr_t)list : (int)strlen(list);
1373 if ((l==len) && !strncasecmp(str, list, len))
1374 return true;
1375 list += sep ? l + 1 : l;
1376 }
1377
1378 return false;
1379}
1380
1381inline static bool str_begins_ends_oneof(const char *str, const char *list, bool begins)
1382{
1383 logf_clauses("%s %s (%s) %s", str, __func__, begins ? "begins" : "ends", list);
1384 const char *sep;
1385 int l, p, len = strlen(str);
1386
1387 while (*list)
1388 {
1389 sep = strchr(list, '|');
1390 l = sep ? (intptr_t)sep - (intptr_t)list : (int)strlen(list);
1391 p = begins ? 0 : len - l;
1392 if (l <= len && !strncasecmp(&str[p], list, l))
1393 return true;
1394 list += sep ? l + 1 : l;
1395 }
1396
1397 return false;
1398}
1399
1400static bool check_against_clause(long numeric, const char *str,
1401 const struct tagcache_search_clause *clause)
1402{
1403 if (clause->numeric)
1404 {
1405 switch (clause->type)
1406 {
1407 case clause_is:
1408 return numeric == clause->numeric_data;
1409 case clause_is_not:
1410 return numeric != clause->numeric_data;
1411 case clause_gt:
1412 return numeric > clause->numeric_data;
1413 case clause_gteq:
1414 return numeric >= clause->numeric_data;
1415 case clause_lt:
1416 return numeric < clause->numeric_data;
1417 case clause_lteq:
1418 return numeric <= clause->numeric_data;
1419 default:
1420 logf("Incorrect numeric tag: %d", clause->type);
1421 }
1422 }
1423 else
1424 {
1425 switch (clause->type)
1426 {
1427 case clause_is:
1428 return !strcasecmp(clause->str, str);
1429 case clause_is_not:
1430 return strcasecmp(clause->str, str);
1431 case clause_gt:
1432 return 0>strcasecmp(clause->str, str);
1433 case clause_gteq:
1434 return 0>=strcasecmp(clause->str, str);
1435 case clause_lt:
1436 return 0<strcasecmp(clause->str, str);
1437 case clause_lteq:
1438 return 0<=strcasecmp(clause->str, str);
1439 case clause_contains:
1440 return (strcasestr(str, clause->str) != NULL);
1441 case clause_not_contains:
1442 return (strcasestr(str, clause->str) == NULL);
1443 case clause_begins_with:
1444 return (strcasestr(str, clause->str) == str);
1445 case clause_not_begins_with:
1446 return (strcasestr(str, clause->str) != str);
1447 case clause_ends_with:
1448 return str_ends_with(str, clause->str);
1449 case clause_not_ends_with:
1450 return !str_ends_with(str, clause->str);
1451 case clause_oneof:
1452 return str_oneof(str, clause->str);
1453 case clause_not_oneof:
1454 return !str_oneof(str, clause->str);
1455 case clause_ends_oneof:
1456 /* Fall-Through */
1457 case clause_begins_oneof:
1458 return str_begins_ends_oneof(str, clause->str,
1459 clause->type == clause_begins_oneof);
1460 case clause_not_ends_oneof:
1461 /* Fall-Through */
1462 case clause_not_begins_oneof:
1463 return !str_begins_ends_oneof(str, clause->str,
1464 clause->type == clause_not_begins_oneof);
1465 default:
1466 logf("Incorrect tag: %d", clause->type);
1467 }
1468 }
1469
1470 return false;
1471}
1472
1473static bool check_clauses(struct tagcache_search *tcs,
1474 struct index_entry *idx,
1475 struct tagcache_search_clause **clauses, int count)
1476{
1477 int i;
1478
1479 /* Go through all conditional clauses. */
1480 for (i = 0; i < count; i++)
1481 {
1482 int seek;
1483 char buf[256];
1484 const int bufsz = sizeof(buf);
1485 char *str = buf;
1486 struct tagcache_search_clause *clause = clauses[i];
1487
1488 logf_clauses("%s clause %d %s %s [%ld] %s",
1489 "Checking", i, tag_type_str[clause->type],
1490 tags_str[clause->tag], clause->numeric_data,
1491 (clause->numeric || clause->str == NULL) ? "[NUMERIC?]" : clause->str);
1492
1493 if (clause->type == clause_logical_or)
1494 {
1495 logf_clauses("Bailing");
1496 break; /* all conditions before logical-or satisfied --
1497 stop processing clauses */
1498 }
1499 seek = check_virtual_tags(clause->tag, tcs->idx_id, idx);
1500
1501#ifdef HAVE_TC_RAMCACHE
1502 if (tcs->ramsearch)
1503 {
1504 struct tagfile_entry *tfe;
1505
1506 if (!TAGCACHE_IS_NUMERIC(clause->tag))
1507 {
1508 if (clause->tag == tag_filename
1509 || clause->tag == tag_virt_basename)
1510 {
1511 retrieve(tcs, IF_DIRCACHE(tcs->idx_id,) idx, clause->tag,
1512 buf, bufsz);
1513 }
1514 else
1515 {
1516 tfe = (struct tagfile_entry *)
1517 &tcramcache.hdr->tags[clause->tag][seek];
1518 /* str points to movable data, but no locking required here,
1519 * as no yield() is following */
1520 str = tfe->tag_data;
1521 }
1522 }
1523 }
1524 else
1525#endif /* HAVE_TC_RAMCACHE */
1526 {
1527 struct tagfile_entry tfe;
1528
1529 if (!TAGCACHE_IS_NUMERIC(clause->tag))
1530 {
1531 int tag = clause->tag;
1532 if (tag == tag_virt_basename)
1533 tag = tag_filename;
1534
1535 int fd = tcs->idxfd[tag];
1536 lseek(fd, seek, SEEK_SET);
1537
1538 switch (read_tagfile_entry_and_tag(fd, &tfe, str, bufsz))
1539 {
1540 case e_SUCCESS_LEN_ZERO: /* Check if entry has been deleted. */
1541 return false;
1542 case e_SUCCESS:
1543 if (clause->tag == tag_virt_basename)
1544 {
1545 char *basename = strrchr(str, '/');
1546 if (basename)
1547 str = basename + 1;
1548 }
1549 break;
1550 case e_ENTRY_SIZEMISMATCH:
1551 logf("read error #15");
1552 return false;
1553 case e_TAG_TOOLONG:
1554 logf("too long tag #6");
1555 return false;
1556 case e_TAG_SIZEMISMATCH:
1557 logf("read error #16");
1558 return false;
1559 default:
1560 logf("unknown_error");
1561 break;;
1562 }
1563 }
1564 }
1565
1566 if (!check_against_clause(seek, str, clause))
1567 {
1568 /* Clause failed -- try finding a logical-or clause */
1569 while (++i < count)
1570 {
1571 if (clauses[i]->type == clause_logical_or)
1572 break;
1573 }
1574
1575 if (i < count) /* Found logical-or? */
1576 continue; /* Check clauses after logical-or */
1577
1578 return false;
1579 }
1580
1581 logf_clauses("%s clause %d %s %s [%ld] %s",
1582 "Found", i, tag_type_str[clause->type],
1583 tags_str[clause->tag], clause->numeric_data,
1584 (clause->numeric || clause->str == NULL) ? "[NUMERIC?]" : clause->str);
1585 }
1586
1587 return true;
1588}
1589
1590bool tagcache_check_clauses(struct tagcache_search *tcs,
1591 struct tagcache_search_clause **clause, int count)
1592{
1593 struct index_entry idx;
1594
1595 if (count == 0)
1596 return true;
1597
1598 if (!get_index(tcs->masterfd, tcs->idx_id, &idx, true))
1599 return false;
1600
1601 return check_clauses(tcs, &idx, clause, count);
1602}
1603
1604static bool add_uniqbuf(struct tagcache_search *tcs, uint32_t id)
1605{
1606 int i;
1607
1608 /* If uniq buffer is not defined we must return true for search to work. */
1609 if (tcs->unique_list == NULL || (!TAGCACHE_IS_UNIQUE(tcs->type)
1610 && !TAGCACHE_IS_NUMERIC(tcs->type)))
1611 {
1612 return true;
1613 }
1614
1615 for (i = 0; i < tcs->unique_list_count; i++)
1616 {
1617 /* Return false if entry is found. */
1618 if (tcs->unique_list[i] == id)
1619 {
1620 //logf("%d Exists @ %d", id, i);
1621 return false;
1622 }
1623 }
1624
1625 if (tcs->unique_list_count < tcs->unique_list_capacity)
1626 {
1627 tcs->unique_list[i] = id;
1628 tcs->unique_list_count++;
1629 }
1630
1631 return true;
1632}
1633
1634static bool build_lookup_list(struct tagcache_search *tcs)
1635{
1636 struct index_entry entry;
1637 int i, j;
1638
1639 tcs->seek_list_count = 0;
1640
1641#ifdef HAVE_TC_RAMCACHE
1642 if (tcs->ramsearch)
1643 {
1644 tcrc_buffer_lock(); /* lock because below makes a pointer to movable data */
1645
1646 for (i = tcs->seek_pos; i < current_tcmh.tch.entry_count; i++)
1647 {
1648 struct tagcache_seeklist_entry *seeklist;
1649 /* idx points to movable data, don't yield or reload */
1650 struct index_entry *idx = &tcramcache.hdr->indices[i];
1651 if (tcs->seek_list_count == SEEK_LIST_SIZE)
1652 break ;
1653
1654 /* Skip deleted files. */
1655 if (idx->flag & FLAG_DELETED)
1656 continue;
1657
1658 /* Go through all filters.. */
1659 for (j = 0; j < tcs->filter_count; j++)
1660 {
1661 if (idx->tag_seek[tcs->filter_tag[j]] != tcs->filter_seek[j])
1662 {
1663 break ;
1664 }
1665 }
1666
1667 if (j < tcs->filter_count)
1668 continue ;
1669
1670 /* Check for conditions. */
1671 if (!check_clauses(tcs, idx, tcs->clause, tcs->clause_count))
1672 continue;
1673 /* Add to the seek list if not already in uniq buffer (doesn't yield)*/
1674 if (!add_uniqbuf(tcs, idx->tag_seek[tcs->type]))
1675 continue;
1676
1677 /* Lets add it. */
1678 seeklist = &tcs->seeklist[tcs->seek_list_count];
1679 seeklist->seek = idx->tag_seek[tcs->type];
1680 seeklist->flag = idx->flag;
1681 seeklist->idx_id = i;
1682 tcs->seek_list_count++;
1683 }
1684
1685 tcrc_buffer_unlock();
1686
1687 tcs->seek_pos = i;
1688
1689 return tcs->seek_list_count > 0;
1690 }
1691#endif /* HAVE_TC_RAMCACHE */
1692
1693 if (tcs->masterfd < 0)
1694 {
1695 struct master_header tcmh;
1696 tcs->masterfd = open_master_fd(&tcmh, false);
1697 }
1698
1699 lseek(tcs->masterfd, tcs->seek_pos * sizeof(struct index_entry) +
1700 sizeof(struct master_header), SEEK_SET);
1701
1702 while (read_index_entries(tcs->masterfd, &entry, 1) == sizeof(struct index_entry))
1703 {
1704 struct tagcache_seeklist_entry *seeklist;
1705
1706 if (tcs->seek_list_count == SEEK_LIST_SIZE)
1707 break ;
1708
1709 i = tcs->seek_pos;
1710 tcs->seek_pos++;
1711
1712 /* Check if entry has been deleted. */
1713 if (entry.flag & FLAG_DELETED)
1714 continue;
1715
1716 /* Go through all filters.. */
1717 for (j = 0; j < tcs->filter_count; j++)
1718 {
1719 if (entry.tag_seek[tcs->filter_tag[j]] != tcs->filter_seek[j])
1720 break ;
1721 }
1722
1723 if (j < tcs->filter_count)
1724 continue ;
1725
1726 /* Check for conditions. */
1727 if (!check_clauses(tcs, &entry, tcs->clause, tcs->clause_count))
1728 continue;
1729
1730 /* Add to the seek list if not already in uniq buffer. */
1731 if (!add_uniqbuf(tcs, entry.tag_seek[tcs->type]))
1732 continue;
1733
1734 /* Lets add it. */
1735 seeklist = &tcs->seeklist[tcs->seek_list_count];
1736 seeklist->seek = entry.tag_seek[tcs->type];
1737 seeklist->flag = entry.flag;
1738 seeklist->idx_id = i;
1739 tcs->seek_list_count++;
1740
1741 yield();
1742 }
1743
1744 return tcs->seek_list_count > 0;
1745}
1746
1747
1748bool tagcache_search(struct tagcache_search *tcs, int tag)
1749{
1750 /* NOTE: call tagcache_search_finish(&tcs) when finished or BAD things may happen (TM) */
1751 struct tagcache_header tag_hdr;
1752 struct master_header master_hdr;
1753 int i;
1754
1755 while (read_lock)
1756 sleep(1);
1757
1758 memset(tcs, 0, sizeof(struct tagcache_search));
1759 if (tc_stat.commit_step > 0 || !tc_stat.ready)
1760 return false;
1761
1762 tcs->position = sizeof(struct tagcache_header);
1763 tcs->type = tag;
1764 tcs->seek_pos = 0;
1765 tcs->list_position = 0;
1766 tcs->seek_list_count = 0;
1767 tcs->filter_count = 0;
1768 tcs->masterfd = -1;
1769
1770 for (i = 0; i < TAG_COUNT; i++)
1771 tcs->idxfd[i] = -1;
1772
1773#ifndef HAVE_TC_RAMCACHE
1774 tcs->ramsearch = false;
1775#else
1776 tcs->ramsearch = tc_stat.ramcache;
1777 if (tcs->ramsearch)
1778 {
1779 tcs->entry_count = tcramcache.hdr->entry_count[tcs->type];
1780 }
1781 else
1782#endif
1783 {
1784 /* Always open as R/W so we can pass tcs to functions that modify data also
1785 * without failing. */
1786 tcs->masterfd = open_master_fd(&master_hdr, true);
1787 if (tcs->masterfd < 0)
1788 return false;
1789
1790 if (!TAGCACHE_IS_NUMERIC(tcs->type))
1791 {
1792 tcs->idxfd[tcs->type] = open_tag_fd(&tag_hdr, tcs->type, false);
1793 if (tcs->idxfd[tcs->type] < 0)
1794 return false;
1795
1796 tcs->entry_count = tag_hdr.entry_count;
1797 }
1798 else
1799 {
1800 tcs->entry_count = master_hdr.tch.entry_count;
1801 }
1802 }
1803
1804 tcs->valid = true;
1805 tcs->initialized = true;
1806 write_lock++;
1807
1808 return true;
1809}
1810
1811void tagcache_search_set_uniqbuf(struct tagcache_search *tcs,
1812 void *buffer, long length)
1813{
1814 tcs->unique_list = (uint32_t *)buffer;
1815 tcs->unique_list_capacity = length / sizeof(*tcs->unique_list);
1816 tcs->unique_list_count = 0;
1817 memset(tcs->unique_list, 0, tcs->unique_list_capacity);
1818}
1819
1820bool tagcache_search_add_filter(struct tagcache_search *tcs,
1821 int tag, int seek)
1822{
1823 if (tcs->filter_count == TAGCACHE_MAX_FILTERS)
1824 return false;
1825
1826 if (TAGCACHE_IS_NUMERIC_OR_NONUNIQUE(tag))
1827 return false;
1828
1829 tcs->filter_tag[tcs->filter_count] = tag;
1830 tcs->filter_seek[tcs->filter_count] = seek;
1831 tcs->filter_count++;
1832
1833 return true;
1834}
1835
1836bool tagcache_search_add_clause(struct tagcache_search *tcs,
1837 struct tagcache_search_clause *clause)
1838{
1839 int i;
1840 int clause_count = tcs->clause_count;
1841
1842 if (clause_count >= TAGCACHE_MAX_CLAUSES)
1843 {
1844 logf("Too many clauses");
1845 return false;
1846 }
1847
1848 if (clause->type != clause_logical_or)
1849 {
1850 /* BUGFIX OR'd clauses seem to be mishandled once made into a filter */
1851 if (clause_count <= 1 || tcs->clause[clause_count - 1]->type != clause_logical_or)
1852 {
1853 /* Check if there is already a similar filter in present (filters are
1854 * much faster than clauses).
1855 */
1856 for (i = 0; i < tcs->filter_count; i++)
1857 {
1858 if (tcs->filter_tag[i] == clause->tag)
1859 {
1860 return true;
1861 }
1862 }
1863 }
1864
1865 if (!TAGCACHE_IS_NUMERIC(clause->tag) && tcs->idxfd[clause->tag] < 0)
1866 {
1867 char fname[MAX_PATH];
1868 tcs->idxfd[clause->tag] = open_pathfmt(fname, sizeof(fname), O_RDONLY,
1869 "%s/" TAGCACHE_FILE_INDEX,
1870 tc_stat.db_path, clause->tag);
1871 }
1872 }
1873
1874 tcs->clause[tcs->clause_count] = clause;
1875 tcs->clause_count++;
1876
1877 return true;
1878}
1879
1880static bool get_next(struct tagcache_search *tcs, bool is_numeric, char *buf, long bufsz)
1881{
1882 struct tagfile_entry entry;
1883#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
1884 long flag = 0;
1885#endif
1886
1887 if (tcs->idxfd[tcs->type] < 0 && !is_numeric
1888#ifdef HAVE_TC_RAMCACHE
1889 && !tcs->ramsearch
1890#endif
1891 )
1892 return false;
1893
1894 /* Relative fetch. */
1895 if (tcs->filter_count > 0 || tcs->clause_count > 0 || is_numeric
1896#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
1897 /* We need to retrieve flag status for dircache. */
1898 || (tcs->ramsearch && tcs->type == tag_filename)
1899#endif
1900 )
1901 {
1902 struct tagcache_seeklist_entry *seeklist;
1903
1904 /* Check for end of list. */
1905 if (tcs->list_position == tcs->seek_list_count)
1906 {
1907 tcs->list_position = 0;
1908
1909 /* Try to fetch more. */
1910 if (!build_lookup_list(tcs))
1911 {
1912 tcs->valid = false;
1913 return false;
1914 }
1915 }
1916
1917 seeklist = &tcs->seeklist[tcs->list_position];
1918#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
1919 flag = seeklist->flag;
1920#endif
1921 tcs->position = seeklist->seek;
1922 tcs->idx_id = seeklist->idx_id;
1923 tcs->list_position++;
1924 }
1925 else
1926 {
1927 if (tcs->entry_count == 0)
1928 {
1929 tcs->valid = false;
1930 return false;
1931 }
1932
1933 tcs->entry_count--;
1934 }
1935
1936 tcs->result_seek = tcs->position;
1937
1938 if (is_numeric)
1939 {
1940 itoa_buf(buf, bufsz, tcs->position);
1941 tcs->result = buf;
1942 tcs->result_len = strlen(buf) + 1;
1943 return true;
1944 }
1945
1946 /* Direct fetch. */
1947#ifdef HAVE_TC_RAMCACHE
1948 if (tcs->ramsearch)
1949 {
1950#ifdef HAVE_DIRCACHE
1951 if (tcs->type == tag_filename && (flag & FLAG_DIRCACHE))
1952 {
1953 ssize_t len = dircache_get_fileref_path(&tcrc_dcfrefs[tcs->idx_id],
1954 buf, bufsz);
1955 if (len >= 0)
1956 {
1957 tcs->result_len = len + 1;
1958 tcs->result = buf;
1959 tcs->ramresult = false;
1960 return true;
1961 }
1962 /* else do it the hard way */
1963 }
1964#endif /* HAVE_DIRCACHE */
1965
1966 if (tcs->type != tag_filename)
1967 {
1968 struct tagfile_entry *ep;
1969
1970 ep = (struct tagfile_entry *)&tcramcache.hdr->tags[tcs->type][tcs->position];
1971 /* don't return ep->tag_data directly as it may move */
1972 tcs->result_len = strlcpy(buf, ep->tag_data, bufsz) + 1;
1973 tcs->result = buf;
1974 tcs->idx_id = ep->idx_id;
1975 tcs->ramresult = false; /* was true before we copied to buf too */
1976
1977 /* Increase position for the next run. This may get overwritten. */
1978 tcs->position += sizeof(struct tagfile_entry) + ep->tag_length;
1979
1980 return true;
1981 }
1982 }
1983#endif /* HAVE_TC_RAMCACHE */
1984
1985 if (!open_files(tcs, tcs->type))
1986 {
1987 tcs->valid = false;
1988 return false;
1989 }
1990
1991 /* Seek stream to the correct position and continue to direct fetch. */
1992 lseek(tcs->idxfd[tcs->type], tcs->position, SEEK_SET);
1993
1994 switch (read_tagfile_entry_and_tag(tcs->idxfd[tcs->type], &entry, buf, bufsz))
1995 {
1996 case e_SUCCESS_LEN_ZERO:
1997 case e_SUCCESS:
1998 break;
1999 case e_ENTRY_SIZEMISMATCH:
2000 logf("read error #5");
2001 tcs->valid = false;
2002 return false;
2003 case e_TAG_TOOLONG:
2004 tcs->valid = false;
2005 logf("too long tag #2");
2006 logf("P:%lX/%" PRIX32, (unsigned long) tcs->position, entry.tag_length);
2007 return false;
2008 case e_TAG_SIZEMISMATCH:
2009 tcs->valid = false;
2010 logf("read error #4");
2011 return false;
2012 }
2013
2014 /**
2015 Update the position for the next read (this may be overridden
2016 if filters or clauses are being used).
2017 */
2018 tcs->position += sizeof(struct tagfile_entry) + entry.tag_length;
2019 str_setlen(buf, entry.tag_length);
2020
2021 tcs->result = buf;
2022 tcs->result_len = entry.tag_length + 1;
2023 tcs->idx_id = entry.idx_id;
2024 tcs->ramresult = false;
2025
2026 return true;
2027}
2028
2029bool tagcache_get_next(struct tagcache_search *tcs, char *buf, long size)
2030{
2031 if (tcs->valid && tagcache_is_usable())
2032 {
2033 bool is_numeric = TAGCACHE_IS_NUMERIC(tcs->type);
2034 while (get_next(tcs, is_numeric, buf, size))
2035 {
2036 if (tcs->result_len > 1)
2037 return true;
2038 }
2039 }
2040#ifdef LOGF_ENABLE
2041 if (tcs->unique_list_count > 0)
2042 logf(" uniqbuf: %d used / %d avail", tcs->unique_list_count, tcs->unique_list_capacity);
2043#endif
2044
2045 return false;
2046}
2047
2048bool tagcache_retrieve(struct tagcache_search *tcs, int idxid,
2049 int tag, char *buf, long size)
2050{
2051 struct index_entry idx;
2052
2053 *buf = '\0';
2054 if (!get_index(tcs->masterfd, idxid, &idx, true))
2055 return false;
2056
2057 return retrieve(tcs, IF_DIRCACHE(idxid,) &idx, tag, buf, size);
2058}
2059
2060void tagcache_search_finish(struct tagcache_search *tcs)
2061{
2062 int i;
2063
2064 if (!tcs->initialized)
2065 return;
2066
2067 if (tcs->masterfd >= 0)
2068 {
2069 close(tcs->masterfd);
2070 tcs->masterfd = -1;
2071 }
2072
2073 for (i = 0; i < TAG_COUNT; i++)
2074 {
2075 if (tcs->idxfd[i] >= 0)
2076 {
2077 close(tcs->idxfd[i]);
2078 tcs->idxfd[i] = -1;
2079 }
2080 }
2081
2082 tcs->ramsearch = false;
2083 tcs->valid = false;
2084 tcs->initialized = 0;
2085 if (write_lock > 0)
2086 write_lock--;
2087}
2088
2089#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
2090static struct tagfile_entry *get_tag(const struct index_entry *entry, int tag)
2091{
2092 return (struct tagfile_entry *)&tcramcache.hdr->tags[tag][entry->tag_seek[tag]];
2093}
2094
2095static long get_tag_numeric(const struct index_entry *entry, int tag, int idx_id)
2096{
2097 return check_virtual_tags(tag, idx_id, entry);
2098}
2099
2100static char* get_tag_string(const struct index_entry *entry, int tag)
2101{
2102 char* s = get_tag(entry, tag)->tag_data;
2103 return strcmp(s, UNTAGGED) ? s : NULL;
2104}
2105
2106bool tagcache_fill_tags(struct mp3entry *id3, const char *filename)
2107{
2108 struct index_entry *entry;
2109 int idx_id;
2110
2111 if (!tc_stat.ready || !tc_stat.ramcache)
2112 return false;
2113
2114 /* Find the corresponding entry in tagcache. */
2115
2116 if (filename != NULL)
2117 memset(id3, 0, sizeof(struct mp3entry));
2118 else /* Note: caller clears id3 prior to call */
2119 filename = id3->path;
2120
2121 idx_id = find_entry_ram(filename);
2122 if (idx_id < 0)
2123 return false;
2124
2125 entry = &tcramcache.hdr->indices[idx_id];
2126
2127 char* buf = id3->id3v2buf;
2128 ssize_t remaining = sizeof(id3->id3v2buf);
2129
2130 /* this macro sets id3 strings by copying to the id3v2buf */
2131#define SET(x, y) do \
2132 { \
2133 if (remaining > 0) \
2134 { \
2135 x = NULL; /* initialize with null if tag doesn't exist */ \
2136 char* src = get_tag_string(entry, y); \
2137 if (src) \
2138 { \
2139 x = buf; \
2140 size_t len = strlcpy(buf, src, remaining) +1; \
2141 buf += len; remaining -= len; \
2142 } \
2143 } \
2144 } while(0)
2145
2146
2147 SET(id3->title, tag_title);
2148 SET(id3->artist, tag_artist);
2149 SET(id3->album, tag_album);
2150 SET(id3->genre_string, tag_genre);
2151 SET(id3->composer, tag_composer);
2152 SET(id3->comment, tag_comment);
2153 SET(id3->albumartist, tag_albumartist);
2154 SET(id3->grouping, tag_grouping);
2155
2156 id3->length = get_tag_numeric(entry, tag_length, idx_id);
2157 id3->playcount = get_tag_numeric(entry, tag_playcount, idx_id);
2158 id3->rating = get_tag_numeric(entry, tag_rating, idx_id);
2159 id3->lastplayed = get_tag_numeric(entry, tag_lastplayed, idx_id);
2160 id3->score = get_tag_numeric(entry, tag_virt_autoscore, idx_id) / 10;
2161 id3->year = get_tag_numeric(entry, tag_year, idx_id);
2162
2163 id3->discnum = get_tag_numeric(entry, tag_discnumber, idx_id);
2164 id3->tracknum = get_tag_numeric(entry, tag_tracknumber, idx_id);
2165 id3->bitrate = get_tag_numeric(entry, tag_bitrate, idx_id);
2166 if (id3->bitrate == 0)
2167 id3->bitrate = 1;
2168
2169 if (global_settings.autoresume_enable)
2170 {
2171 id3->elapsed = get_tag_numeric(entry, tag_lastelapsed, idx_id);
2172 logf("tagcache_fill_tags: Set elapsed for %s to %lX\n",
2173 id3->title, id3->elapsed);
2174
2175 id3->offset = get_tag_numeric(entry, tag_lastoffset, idx_id);
2176 logf("tagcache_fill_tags: Set offset for %s to %lX\n",
2177 id3->title, id3->offset);
2178 }
2179
2180 return true;
2181}
2182#endif /* defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) */
2183
2184static inline void write_item(const char *item)
2185{
2186 int len = strlen(item) + 1;
2187
2188 data_size += len;
2189 write(cachefd, item, len);
2190}
2191
2192static int check_if_empty(char **tag)
2193{
2194 int length;
2195
2196 if (*tag == NULL || **tag == '\0')
2197 {
2198 *tag = UNTAGGED;
2199 return sizeof(UNTAGGED); /* Tag length */
2200 }
2201
2202 length = strlen(*tag);
2203 if (length > TAG_MAXLEN)
2204 {
2205 logf("over length tag: %s", *tag);
2206 length = TAG_MAXLEN;
2207 str_setlen((*tag), length);
2208 }
2209
2210 return length + 1;
2211}
2212
2213#ifdef __PCTOOL__
2214static void db_log(const char *prefix, const char *msg)
2215{
2216 /* Crude logging for the sim - to aid in debugging */
2217 int logfd = open(ROCKBOX_DIR "/database.log",
2218 O_WRONLY | O_APPEND | O_CREAT, 0666);
2219 if (logfd >= 0)
2220 {
2221 write(logfd, prefix, strlen(prefix));
2222 write(logfd, ": ", 2);
2223 write(logfd, msg, strlen(msg));
2224 write(logfd, "\n", 1);
2225 close(logfd);
2226 }
2227}
2228#endif
2229
2230/* GCC 3.4.6 for Coldfire can choose to inline this function. Not a good
2231 * idea, as it uses lots of stack and is called from a recursive function
2232 * (check_dir).
2233 */
2234static void NO_INLINE add_tagcache(char *path, unsigned long mtime)
2235{
2236 #define ADD_TAG(entry, tag, data) \
2237 /* Adding tag */ \
2238 entry.tag_length[tag] = check_if_empty(data); \
2239 entry.tag_offset[tag] = offset; \
2240 offset += entry.tag_length[tag]
2241
2242 struct mp3entry id3;
2243 struct temp_file_entry entry;
2244 bool ret;
2245 int idx_id = -1;
2246 char tracknumfix[3];
2247 int offset = 0;
2248 int path_length = strlen(path);
2249 bool has_artist;
2250 bool has_grouping;
2251
2252 DB_LOG("file", path);
2253
2254 if (cachefd < 0)
2255 return ;
2256
2257 /* Check for overlength file path. */
2258 if (path_length > MAX_PATH || path_length > TAG_MAXLEN)
2259 {
2260 /* Path can't be shortened. */
2261 logf("Too long path: %s", path);
2262 DB_LOG("error", "path too long");
2263 return ;
2264 }
2265
2266 /* Check if the file is supported. */
2267 if (probe_file_format(path) == AFMT_UNKNOWN)
2268 return ;
2269
2270 /* Check if the file is already cached. */
2271#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
2272 idx_id = find_entry_ram(path);
2273#endif
2274
2275 /* Be sure the entry doesn't exist. */
2276 if (filenametag_fd >= 0 && idx_id < 0)
2277 idx_id = find_entry_disk(path, false);
2278
2279 /* Check if file has been modified. */
2280 if (idx_id >= 0)
2281 {
2282 struct index_entry idx;
2283
2284 /* TODO: Mark that the index exists (for fast reverse scan) */
2285 //found_idx[idx_id/8] |= idx_id%8;
2286
2287 if (!get_index(-1, idx_id, &idx, true))
2288 {
2289 logf("failed to retrieve index entry");
2290 DB_LOG("error", "failed to retrieve index entry");
2291 return ;
2292 }
2293
2294 if ((unsigned long)idx.tag_seek[tag_mtime] == mtime)
2295 {
2296 /* No changes to file. */
2297 return ;
2298 }
2299
2300 /* Metadata might have been changed. Delete the entry. */
2301 logf("Re-adding: %s", path);
2302 DB_LOG("info", "re-adding");
2303 if (!delete_entry(idx_id))
2304 {
2305 logf("delete_entry failed: %d", idx_id);
2306 DB_LOG("error", "delete entry failed");
2307 return ;
2308 }
2309 }
2310
2311 /*memset(&id3, 0, sizeof(struct mp3entry)); -- get_metadata does this for us */
2312 memset(&entry, 0, sizeof(struct temp_file_entry));
2313 memset(&tracknumfix, 0, sizeof(tracknumfix));
2314 ret = get_metadata_ex(&id3, -1, path, METADATA_EXCLUDE_ID3_PATH);
2315
2316 if (!ret)
2317 {
2318 logf("get_metadata failed: %s", path);
2319 DB_LOG("error", "get_metadata failed");
2320 return ;
2321 }
2322
2323 logf("-> %s", path);
2324
2325 if (id3.tracknum <= 0) /* Track number missing? */
2326 {
2327 id3.tracknum = -1;
2328 }
2329
2330 /* Numeric tags */
2331 entry.tag_offset[tag_year] = id3.year;
2332 entry.tag_offset[tag_discnumber] = id3.discnum;
2333 entry.tag_offset[tag_tracknumber] = id3.tracknum;
2334 entry.tag_offset[tag_length] = id3.length;
2335 entry.tag_offset[tag_bitrate] = id3.bitrate;
2336 entry.tag_offset[tag_mtime] = mtime;
2337
2338 /* String tags. */
2339 has_artist = id3.artist != NULL
2340 && strlen(id3.artist) > 0;
2341 has_grouping = id3.grouping != NULL
2342 && strlen(id3.grouping) > 0;
2343
2344 ADD_TAG(entry, tag_filename, &path);
2345 ADD_TAG(entry, tag_title, &id3.title);
2346 ADD_TAG(entry, tag_artist, &id3.artist);
2347 ADD_TAG(entry, tag_album, &id3.album);
2348 ADD_TAG(entry, tag_genre, &id3.genre_string);
2349 ADD_TAG(entry, tag_composer, &id3.composer);
2350 ADD_TAG(entry, tag_comment, &id3.comment);
2351 ADD_TAG(entry, tag_albumartist, &id3.albumartist);
2352 if (has_artist)
2353 {
2354 ADD_TAG(entry, tag_virt_canonicalartist, &id3.artist);
2355 }
2356 else
2357 {
2358 ADD_TAG(entry, tag_virt_canonicalartist, &id3.albumartist);
2359 }
2360 if (has_grouping)
2361 {
2362 ADD_TAG(entry, tag_grouping, &id3.grouping);
2363 }
2364 else
2365 {
2366 ADD_TAG(entry, tag_grouping, &id3.title);
2367 }
2368 entry.data_length = offset;
2369
2370 /* Write the header */
2371 write(cachefd, &entry, sizeof(struct temp_file_entry));
2372
2373 /* And tags also... Correct order is critical */
2374 write_item(path);
2375 write_item(id3.title);
2376 write_item(id3.artist);
2377 write_item(id3.album);
2378 write_item(id3.genre_string);
2379 write_item(id3.composer);
2380 write_item(id3.comment);
2381 write_item(id3.albumartist);
2382 if (has_artist)
2383 {
2384 write_item(id3.artist);
2385 }
2386 else
2387 {
2388 write_item(id3.albumartist);
2389 }
2390 if (has_grouping)
2391 {
2392 write_item(id3.grouping);
2393 }
2394 else
2395 {
2396 write_item(id3.title);
2397 }
2398
2399 total_entry_count++;
2400
2401 #undef ADD_TAG
2402}
2403#endif /*!defined(PLUGIN)*/
2404
2405
2406static bool tempbuf_insert(char *str, int id, int idx_id, bool unique)
2407{
2408 struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf;
2409 int len = strlen(str)+1;
2410 int i;
2411 unsigned *crcbuf = (unsigned *)&tempbuf[tempbuf_size-4];
2412 unsigned crc32 = 0xffffffff;
2413 char chr_lower;
2414 for (i = 0; str[i] != '\0' && i < len -1; i++)
2415 {
2416 chr_lower = tolower(str[i]);
2417 crc32 = crc_32(&chr_lower, 1, crc32);
2418 }
2419
2420 if (unique)
2421 {
2422 /* Check if the crc does not exist -> entry does not exist for sure. */
2423 for (i = 0; i < tempbufidx; i++)
2424 {
2425 if (crcbuf[-i] != crc32)
2426 continue;
2427
2428 if (!strcasecmp(str, index[i].str))
2429 {
2430 if (id < 0 || id >= lookup_buffer_depth)
2431 {
2432 logf("lookup buf overf.: %d", id);
2433 return false;
2434 }
2435
2436 lookup[id] = &index[i];
2437 return true;
2438 }
2439 }
2440 }
2441
2442 /* Insert to CRC buffer. */
2443 crcbuf[-tempbufidx] = crc32;
2444 tempbuf_left -= 4;
2445
2446 /* Insert it to the buffer. */
2447 tempbuf_left -= len;
2448 if (tempbuf_left - 4 < 0 || tempbufidx >= commit_entry_count)
2449 {
2450 logf("temp buf error rem: %ld idx: %ld / %ld",
2451 tempbuf_left, tempbufidx, commit_entry_count-1);
2452 return false;
2453 }
2454 if (id >= lookup_buffer_depth)
2455 {
2456 logf("lookup buf overf. #2: %d", id);
2457 return false;
2458 }
2459
2460 if (id >= 0)
2461 {
2462 lookup[id] = &index[tempbufidx];
2463 index[tempbufidx].idlist.id = id;
2464 }
2465 else
2466 index[tempbufidx].idlist.id = -1;
2467
2468 index[tempbufidx].idlist.next = NULL;
2469 index[tempbufidx].idx_id = idx_id;
2470 index[tempbufidx].seek = -1;
2471 index[tempbufidx].str = &tempbuf[tempbuf_pos];
2472 memcpy(index[tempbufidx].str, str, len);
2473 tempbuf_pos += len;
2474 tempbufidx++;
2475
2476 return true;
2477}
2478
2479static int compare(const void *p1, const void *p2)
2480{
2481 do_timed_yield();
2482
2483 struct tempbuf_searchidx *e1 = (struct tempbuf_searchidx *)p1;
2484 struct tempbuf_searchidx *e2 = (struct tempbuf_searchidx *)p2;
2485
2486 if (strcmp(e1->str, UNTAGGED) == 0)
2487 {
2488 if (strcmp(e2->str, UNTAGGED) == 0)
2489 return 0;
2490 return -1;
2491 }
2492 else if (strcmp(e2->str, UNTAGGED) == 0)
2493 return 1;
2494
2495 return strncasecmp(e1->str, e2->str, TAG_MAXLEN);
2496}
2497
2498static int tempbuf_sort(int fd)
2499{
2500 struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf;
2501 struct tagfile_entry fe;
2502 int i;
2503 int length;
2504
2505 /* Generate reverse lookup entries. */
2506 for (i = 0; i < lookup_buffer_depth; i++)
2507 {
2508 struct tempbuf_id_list *idlist;
2509
2510 if (!lookup[i])
2511 continue;
2512
2513 if (lookup[i]->idlist.id == i)
2514 continue;
2515
2516 idlist = &lookup[i]->idlist;
2517 while (idlist->next != NULL)
2518 idlist = idlist->next;
2519
2520 ALIGN_BUFFER(tempbuf_pos, tempbuf_left, alignof(struct tempbuf_id_list));
2521 tempbuf_left -= sizeof(struct tempbuf_id_list);
2522 if (tempbuf_left < 0)
2523 return -1;
2524
2525 idlist->next = (struct tempbuf_id_list *)&tempbuf[tempbuf_pos];
2526 tempbuf_pos += sizeof(struct tempbuf_id_list);
2527
2528 idlist = idlist->next;
2529 idlist->id = i;
2530 idlist->next = NULL;
2531
2532 do_timed_yield();
2533 }
2534
2535 qsort(index, tempbufidx, sizeof(struct tempbuf_searchidx), compare);
2536 memset(lookup, 0, lookup_buffer_depth * sizeof(struct tempbuf_searchidx **));
2537
2538 for (i = 0; i < tempbufidx; i++)
2539 {
2540 struct tempbuf_id_list *idlist = &index[i].idlist;
2541
2542 /* Fix the lookup list. */
2543 while (idlist != NULL)
2544 {
2545 if (idlist->id >= 0)
2546 lookup[idlist->id] = &index[i];
2547 idlist = idlist->next;
2548 }
2549
2550 index[i].seek = lseek(fd, 0, SEEK_CUR);
2551 length = strlen(index[i].str) + 1;
2552 fe.tag_length = length;
2553 fe.idx_id = index[i].idx_id;
2554
2555 /* Check the chunk alignment. */
2556 if ((fe.tag_length + sizeof(struct tagfile_entry))
2557 % TAGFILE_ENTRY_CHUNK_LENGTH)
2558 {
2559 fe.tag_length += TAGFILE_ENTRY_CHUNK_LENGTH -
2560 ((fe.tag_length + sizeof(struct tagfile_entry))
2561 % TAGFILE_ENTRY_CHUNK_LENGTH);
2562 }
2563
2564 if (write_tagfile_entry(fd, &fe) != sizeof(struct tagfile_entry))
2565 {
2566 logf("tempbuf_sort: write error #1");
2567 return -1;
2568 }
2569
2570 if (write(fd, index[i].str, length) != length)
2571 {
2572 logf("tempbuf_sort: write error #2");
2573 return -2;
2574 }
2575
2576 /* Write some padding. */
2577 if (fe.tag_length - length > 0)
2578 write(fd, "XXXXXXXX", fe.tag_length - length);
2579 }
2580
2581 return i;
2582}
2583
2584inline static struct tempbuf_searchidx* tempbuf_locate(int id)
2585{
2586 if (id < 0 || id >= lookup_buffer_depth)
2587 return NULL;
2588
2589 return lookup[id];
2590}
2591
2592
2593inline static int tempbuf_find_location(int id)
2594{
2595 struct tempbuf_searchidx *entry;
2596
2597 entry = tempbuf_locate(id);
2598 if (entry == NULL)
2599 return -1;
2600
2601 return entry->seek;
2602}
2603
2604static bool build_numeric_indices(struct tagcache_header *h, int tmpfd)
2605{
2606 struct master_header tcmh;
2607 struct index_entry idx;
2608 int masterfd;
2609 int masterfd_pos;
2610 struct temp_file_entry *entrybuf = (struct temp_file_entry *)tempbuf;
2611 int max_entries;
2612 int entries_processed = 0;
2613 int i, j;
2614
2615 max_entries = tempbuf_size / sizeof(struct temp_file_entry) - 1;
2616
2617 logf("Building numeric indices...");
2618 lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET);
2619
2620 if ( (masterfd = open_master_fd(&tcmh, true)) < 0)
2621 return false;
2622
2623 masterfd_pos = lseek(masterfd, tcmh.tch.entry_count * sizeof(struct index_entry),
2624 SEEK_CUR);
2625 if (masterfd_pos < 0)
2626 {
2627 logf("we can't append!");
2628 close(masterfd);
2629 return false;
2630 }
2631
2632 while (entries_processed < h->entry_count && !USR_CANCEL)
2633 {
2634 int count = MIN(h->entry_count - entries_processed, max_entries);
2635
2636 /* Read in as many entries as possible. */
2637 for (i = 0; i < count; i++)
2638 {
2639 struct temp_file_entry *tfe = &entrybuf[i];
2640 int datastart;
2641
2642 /* Read in numeric data. */
2643 if (read(tmpfd, tfe, sizeof(struct temp_file_entry)) !=
2644 sizeof(struct temp_file_entry))
2645 {
2646 logf("read fail #1");
2647 close(masterfd);
2648 return false;
2649 }
2650
2651 datastart = lseek(tmpfd, 0, SEEK_CUR);
2652
2653 /**
2654 * Read string data from the following tags:
2655 * - tag_filename
2656 * - tag_artist
2657 * - tag_album
2658 * - tag_title
2659 *
2660 * A crc32 hash is calculated from the read data
2661 * and stored back to the data offset field kept in memory.
2662 */
2663#define tmpdb_read_string_tag(tag) \
2664 lseek(tmpfd, tfe->tag_offset[tag], SEEK_CUR); \
2665 if ((unsigned long)tfe->tag_length[tag] > (unsigned long)build_idx_bufsz) \
2666 { \
2667 logf("read fail: buffer overflow"); \
2668 close(masterfd); \
2669 return false; \
2670 } \
2671 \
2672 if (read(tmpfd, build_idx_buf, tfe->tag_length[tag]) != \
2673 tfe->tag_length[tag]) \
2674 { \
2675 logf("read fail #2"); \
2676 close(masterfd); \
2677 return false; \
2678 } \
2679 str_setlen(build_idx_buf, tfe->tag_length[tag]); \
2680 \
2681 tfe->tag_offset[tag] = crc_32(build_idx_buf, strlen(build_idx_buf), 0xffffffff); \
2682 lseek(tmpfd, datastart, SEEK_SET)
2683
2684 tmpdb_read_string_tag(tag_filename);
2685 tmpdb_read_string_tag(tag_artist);
2686 tmpdb_read_string_tag(tag_album);
2687 tmpdb_read_string_tag(tag_title);
2688
2689 /* Seek to the end of the string data. */
2690 lseek(tmpfd, tfe->data_length, SEEK_CUR);
2691 }
2692
2693 /* Backup the master index position. */
2694 masterfd_pos = lseek(masterfd, 0, SEEK_CUR);
2695 lseek(masterfd, sizeof(struct master_header), SEEK_SET);
2696
2697 /* Check if we can resurrect some deleted runtime statistics data. */
2698 for (i = 0; i < tcmh.tch.entry_count && !USR_CANCEL; i++)
2699 {
2700 /* Read the index entry. */
2701 if (read_index_entries(masterfd, &idx, 1) != sizeof(struct index_entry))
2702 {
2703 logf("read fail #3");
2704 close(masterfd);
2705 return false;
2706 }
2707
2708 /**
2709 * Skip unless the entry is marked as being deleted
2710 * or the data has already been resurrected.
2711 */
2712 if (!(idx.flag & FLAG_DELETED) || (idx.flag & FLAG_RESURRECTED))
2713 continue;
2714
2715 /* Now try to match the entry. */
2716 /**
2717 * To succesfully match a song, the following conditions
2718 * must apply:
2719 *
2720 * For numeric fields: tag_length
2721 * - Full identical match is required
2722 *
2723 * If tag_filename matches, no further checking necessary.
2724 *
2725 * For string hashes: tag_artist, tag_album, tag_title
2726 * - All three of these must match
2727 */
2728 for (j = 0; j < count; j++)
2729 {
2730 struct temp_file_entry *tfe = &entrybuf[j];
2731
2732 /* Try to match numeric fields first. */
2733 if (tfe->tag_offset[tag_length] != idx.tag_seek[tag_length])
2734 continue;
2735
2736 /* Now it's time to do the hash matching. */
2737 if (tfe->tag_offset[tag_filename] != idx.tag_seek[tag_filename])
2738 {
2739 int match_count = 0;
2740
2741 /* No filename match, check if we can match two other tags. */
2742#define tmpdb_match(tag) \
2743 if (tfe->tag_offset[tag] == idx.tag_seek[tag]) \
2744 match_count++
2745
2746 tmpdb_match(tag_artist);
2747 tmpdb_match(tag_album);
2748 tmpdb_match(tag_title);
2749
2750 if (match_count < 3)
2751 {
2752 /* Still no match found, give up. */
2753 continue;
2754 }
2755 }
2756
2757 /* A match found, now copy & resurrect the statistical data. */
2758#define tmpdb_copy_tag(tag) \
2759 tfe->tag_offset[tag] = idx.tag_seek[tag]
2760
2761 tmpdb_copy_tag(tag_playcount);
2762 tmpdb_copy_tag(tag_rating);
2763 tmpdb_copy_tag(tag_playtime);
2764 tmpdb_copy_tag(tag_lastplayed);
2765 tmpdb_copy_tag(tag_commitid);
2766 tmpdb_copy_tag(tag_lastelapsed);
2767 tmpdb_copy_tag(tag_lastoffset);
2768
2769 /* Avoid processing this entry again. */
2770 idx.flag |= FLAG_RESURRECTED;
2771
2772 lseek(masterfd, -(off_t)sizeof(struct index_entry), SEEK_CUR);
2773 if (write_index_entries(masterfd, &idx, 1) != sizeof(struct index_entry))
2774 {
2775 logf("masterfd writeback fail #1");
2776 close(masterfd);
2777 return false;
2778 }
2779
2780 logf("Entry resurrected");
2781 }
2782 }
2783
2784
2785 /* Restore the master index position. */
2786 lseek(masterfd, masterfd_pos, SEEK_SET);
2787
2788 /* Commit the data to the index. */
2789 for (i = 0; i < count && !USR_CANCEL; i++)
2790 {
2791 int loc = lseek(masterfd, 0, SEEK_CUR);
2792
2793 if (read_index_entries(masterfd, &idx, 1) != sizeof(struct index_entry))
2794 {
2795 logf("read fail #3");
2796 close(masterfd);
2797 return false;
2798 }
2799
2800 for (j = 0; j < TAG_COUNT; j++)
2801 {
2802 if (!TAGCACHE_IS_NUMERIC(j))
2803 continue;
2804
2805 idx.tag_seek[j] = entrybuf[i].tag_offset[j];
2806 }
2807 idx.flag = entrybuf[i].flag;
2808
2809 if (idx.tag_seek[tag_commitid])
2810 {
2811 /* Data has been resurrected. */
2812 idx.flag |= FLAG_DIRTYNUM;
2813 }
2814 else if (tc_stat.ready && current_tcmh.commitid > 0)
2815 {
2816 idx.tag_seek[tag_commitid] = current_tcmh.commitid;
2817 idx.flag |= FLAG_DIRTYNUM;
2818 }
2819
2820 /* Write back the updated index. */
2821 lseek(masterfd, loc, SEEK_SET);
2822 if (write_index_entries(masterfd, &idx, 1) != sizeof(struct index_entry))
2823 {
2824 logf("write fail");
2825 close(masterfd);
2826 return false;
2827 }
2828 }
2829
2830 entries_processed += count;
2831 logf("%d/%" PRId32 " entries processed", entries_processed, h->entry_count);
2832 }
2833
2834 close(masterfd);
2835
2836 return true;
2837}
2838
2839/**
2840 * Return values:
2841 * > 0 success
2842 * == 0 temporary failure
2843 * < 0 fatal error
2844 */
2845static int build_index(int index_type, struct tagcache_header *h, int tmpfd)
2846{
2847 int i;
2848 struct tagcache_header tch;
2849 struct master_header tcmh;
2850 struct index_entry idxbuf[IDX_BUF_DEPTH];
2851 int idxbuf_pos;
2852 int fd = -1, masterfd;
2853 bool error = false;
2854 int init;
2855 int masterfd_pos;
2856
2857 logf("Building index: %d", index_type);
2858
2859 /* Check the number of entries we need to allocate ram for. */
2860 commit_entry_count = h->entry_count + 1;
2861
2862 masterfd = open_master_fd(&tcmh, false);
2863 if (masterfd >= 0)
2864 {
2865 commit_entry_count += tcmh.tch.entry_count;
2866 close(masterfd);
2867 }
2868 else
2869 remove_files(); /* Just to be sure we are clean. */
2870
2871 /* Open the index file, which contains the tag names. */
2872 fd = open_tag_fd(&tch, index_type, true);
2873 if (fd >= 0)
2874 {
2875 logf("tch.datasize=%" PRId32, tch.datasize);
2876 lookup_buffer_depth = 1 +
2877 /* First part */ commit_entry_count +
2878 /* Second part */ (tch.datasize / TAGFILE_ENTRY_CHUNK_LENGTH);
2879 }
2880 else
2881 {
2882 lookup_buffer_depth = 1 +
2883 /* First part */ commit_entry_count +
2884 /* Second part */ 0;
2885 }
2886
2887 logf("lookup_buffer_depth=%ld", lookup_buffer_depth);
2888 logf("commit_entry_count=%ld", commit_entry_count);
2889
2890 /* Allocate buffer for all index entries from both old and new
2891 * tag files. */
2892 tempbufidx = 0;
2893 tempbuf_pos = commit_entry_count * sizeof(struct tempbuf_searchidx);
2894
2895 /* Allocate lookup buffer. The first portion of commit_entry_count
2896 * contains the new tags in the temporary file and the second
2897 * part for locating entries already in the db.
2898 *
2899 * New tags Old tags
2900 * +---------+---------------------------+
2901 * | index | position/ENTRY_CHUNK_SIZE | lookup buffer
2902 * +---------+---------------------------+
2903 *
2904 * Old tags are inserted to a temporary buffer with position:
2905 * tempbuf_insert(position/ENTRY_CHUNK_SIZE, ...);
2906 * And new tags with index:
2907 * tempbuf_insert(idx, ...);
2908 *
2909 * The buffer is sorted and written into tag file:
2910 * tempbuf_sort(...);
2911 * leaving master index locations messed up.
2912 *
2913 * That is fixed using the lookup buffer for old tags:
2914 * new_seek = tempbuf_find_location(old_seek, ...);
2915 * and for new tags:
2916 * new_seek = tempbuf_find_location(idx);
2917 */
2918 lookup = (struct tempbuf_searchidx **)&tempbuf[tempbuf_pos];
2919 tempbuf_pos += lookup_buffer_depth * sizeof(void **);
2920 memset(lookup, 0, lookup_buffer_depth * sizeof(void **));
2921
2922 /* And calculate the remaining data space used mainly for storing
2923 * tag data (strings). */
2924 tempbuf_left = tempbuf_size - tempbuf_pos - 8;
2925 if (tempbuf_left - TAGFILE_ENTRY_AVG_LENGTH * commit_entry_count < 0)
2926 {
2927 logf("Buffer way too small!");
2928 close(fd);
2929 return 0;
2930 }
2931
2932 if (fd >= 0)
2933 {
2934 /**
2935 * If tag file contains unique tags (sorted index), we will load
2936 * it entirely into memory so we can resort it later for use with
2937 * chunked browsing.
2938 */
2939 if (TAGCACHE_IS_SORTED(index_type))
2940 {
2941 logf("loading tags...");
2942 for (i = 0; i < tch.entry_count && !USR_CANCEL; i++)
2943 {
2944 struct tagfile_entry entry;
2945 int loc = lseek(fd, 0, SEEK_CUR);
2946 bool ret;
2947 switch (read_tagfile_entry_and_tag(fd, &entry, build_idx_buf, build_idx_bufsz))
2948 {
2949 case e_SUCCESS_LEN_ZERO: /* Skip deleted entries. */
2950 continue;
2951 case e_SUCCESS:
2952 break;
2953 case e_ENTRY_SIZEMISMATCH:
2954 logf("read error #7");
2955 close(fd);
2956 return -2;
2957 case e_TAG_TOOLONG:
2958 logf("too long tag #3");
2959 close(fd);
2960 return -2;
2961 case e_TAG_SIZEMISMATCH:
2962 logf("read error #8");
2963 close(fd);
2964 return -2;
2965 }
2966
2967 /**
2968 * Save the tag and tag id in the memory buffer. Tag id
2969 * is saved so we can later reindex the master lookup
2970 * table when the index gets resorted.
2971 */
2972 ret = tempbuf_insert(build_idx_buf, loc/TAGFILE_ENTRY_CHUNK_LENGTH
2973 + commit_entry_count, entry.idx_id,
2974 TAGCACHE_IS_UNIQUE(index_type));
2975 if (!ret)
2976 {
2977 close(fd);
2978 return -3;
2979 }
2980 do_timed_yield();
2981 }
2982 logf("done");
2983 }
2984 else
2985 tempbufidx = tch.entry_count;
2986 }
2987 else
2988 {
2989 logf("Create New Index: %d", index_type);
2990 /**
2991 * Creating new index file to store the tags. No need to preload
2992 * anything whether the index type is sorted or not.
2993 *
2994 * Note: although we are creating a file under the db path, it must
2995 * already exist by this point so no mkdir is required.
2996 */
2997 fd = open_pathfmt(build_idx_buf, build_idx_bufsz,
2998 O_WRONLY | O_CREAT | O_TRUNC,
2999 "%s/" TAGCACHE_FILE_INDEX,
3000 tc_stat.db_path, index_type);
3001 if (fd < 0)
3002 {
3003 logf(TAGCACHE_FILE_INDEX " open fail", index_type);
3004 return -2;
3005 }
3006
3007 tch.magic = TAGCACHE_MAGIC;
3008 tch.entry_count = 0;
3009 tch.datasize = 0;
3010
3011 if (write_tagcache_header(fd, &tch) != sizeof(struct tagcache_header))
3012 {
3013 logf("header write failed");
3014 close(fd);
3015 return -2;
3016 }
3017 }
3018
3019 /* Loading the tag lookup file as "master file". */
3020 logf("Loading index file");
3021 masterfd = open_db_fd(TAGCACHE_FILE_MASTER, O_RDWR);
3022
3023 if (masterfd < 0)
3024 {
3025 logf("Creating new DB");
3026 masterfd = open_db_fd(TAGCACHE_FILE_MASTER, O_WRONLY | O_CREAT | O_TRUNC);
3027
3028 if (masterfd < 0)
3029 {
3030 logf("Failure to create index file (%s)", TAGCACHE_FILE_MASTER);
3031 close(fd);
3032 return -2;
3033 }
3034
3035 /* Write the header (write real values later). */
3036 memset(&tcmh, 0, sizeof(struct master_header));
3037 tcmh.tch = *h;
3038 tcmh.tch.entry_count = 0;
3039 tcmh.tch.datasize = 0;
3040 tcmh.dirty = true;
3041 write_master_header(masterfd, &tcmh);
3042 init = true;
3043 masterfd_pos = lseek(masterfd, 0, SEEK_CUR);
3044 }
3045 else
3046 {
3047 /**
3048 * Master file already exists so we need to process the current
3049 * file first.
3050 */
3051 init = false;
3052
3053 if (read_master_header(masterfd, &tcmh) != sizeof(struct master_header) ||
3054 tcmh.tch.magic != TAGCACHE_MAGIC)
3055 {
3056 logf("header error");
3057 close(fd);
3058 close(masterfd);
3059 return -2;
3060 }
3061
3062 /**
3063 * If we reach end of the master file, we need to expand it to
3064 * hold new tags. If the current index is not sorted, we can
3065 * simply append new data to end of the file.
3066 * However, if the index is sorted, we need to update all tag
3067 * pointers in the master file for the current index.
3068 */
3069 masterfd_pos = lseek(masterfd, tcmh.tch.entry_count * sizeof(struct index_entry),
3070 SEEK_CUR);
3071 if (masterfd_pos == filesize(masterfd))
3072 {
3073 logf("appending...");
3074 init = true;
3075 }
3076 }
3077
3078 /**
3079 * Load new unique tags in memory to be sorted later and added
3080 * to the master lookup file.
3081 */
3082 if (TAGCACHE_IS_SORTED(index_type))
3083 {
3084 lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET);
3085 /* h is the header of the temporary file containing new tags. */
3086 logf("inserting new tags...");
3087 for (i = 0; i < h->entry_count && !USR_CANCEL; i++)
3088 {
3089 struct temp_file_entry entry;
3090
3091 if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) !=
3092 sizeof(struct temp_file_entry))
3093 {
3094 logf("read fail #3");
3095 error = true;
3096 goto error_exit;
3097 }
3098
3099 /* Read data. */
3100 if (entry.tag_length[index_type] >= build_idx_bufsz)
3101 {
3102 logf("too long entry!");
3103 error = true;
3104 goto error_exit;
3105 }
3106
3107 lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR);
3108 if (read(tmpfd, build_idx_buf, entry.tag_length[index_type]) !=
3109 entry.tag_length[index_type])
3110 {
3111 logf("read fail #4");
3112 error = true;
3113 goto error_exit;
3114 }
3115 str_setlen(build_idx_buf, entry.tag_length[index_type]);
3116
3117#if defined(PLUGIN)
3118 if (user_check_tag(index_type, build_idx_buf))
3119#endif /*defined(PLUGIN)*/
3120 {
3121 if (TAGCACHE_IS_UNIQUE(index_type))
3122 error = !tempbuf_insert(build_idx_buf, i, -1, true);
3123 else
3124 error = !tempbuf_insert(build_idx_buf, i,
3125 tcmh.tch.entry_count + i, false);
3126
3127 if (error)
3128 {
3129 logf("insert error");
3130 goto error_exit;
3131 }
3132 }
3133 /* Skip to next. */
3134 lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] -
3135 entry.tag_length[index_type], SEEK_CUR);
3136 do_timed_yield();
3137 }
3138 logf("done");
3139
3140 /* Sort the buffer data and write it to the index file. */
3141 lseek(fd, sizeof(struct tagcache_header), SEEK_SET);
3142 /**
3143 * We need to truncate the index file now. There can be junk left
3144 * at the end of file (however, we _should_ always follow the
3145 * entry_count and don't crash with that).
3146 */
3147 ftruncate(fd, lseek(fd, 0, SEEK_CUR));
3148
3149 i = tempbuf_sort(fd);
3150 if (i < 0)
3151 goto error_exit;
3152 logf("sorted %d tags", i);
3153
3154 /**
3155 * Now update all indexes in the master lookup file.
3156 */
3157 logf("updating indices...");
3158 lseek(masterfd, sizeof(struct master_header), SEEK_SET);
3159 for (i = 0; i < tcmh.tch.entry_count && !USR_CANCEL; i += idxbuf_pos)
3160 {
3161 int j;
3162 int loc = lseek(masterfd, 0, SEEK_CUR);
3163
3164 idxbuf_pos = MIN(tcmh.tch.entry_count - i, IDX_BUF_DEPTH);
3165
3166 if (read_index_entries(masterfd, idxbuf, idxbuf_pos) !=
3167 (ssize_t)sizeof(struct index_entry) * idxbuf_pos)
3168 {
3169 logf("read fail #5");
3170 error = true;
3171 goto error_exit ;
3172 }
3173 lseek(masterfd, loc, SEEK_SET);
3174
3175 for (j = 0; j < idxbuf_pos; j++)
3176 {
3177 if (idxbuf[j].flag & FLAG_DELETED)
3178 {
3179 /* We can just ignore deleted entries. */
3180 // idxbuf[j].tag_seek[index_type] = 0;
3181 continue;
3182 }
3183
3184 idxbuf[j].tag_seek[index_type] = tempbuf_find_location(
3185 idxbuf[j].tag_seek[index_type]/TAGFILE_ENTRY_CHUNK_LENGTH
3186 + commit_entry_count);
3187
3188 if (idxbuf[j].tag_seek[index_type] < 0)
3189 {
3190 logf("update error: %" PRId32 "/%d/%" PRId32,
3191 idxbuf[j].flag, i+j, tcmh.tch.entry_count);
3192 error = true;
3193 goto error_exit;
3194 }
3195
3196 do_timed_yield();
3197 }
3198
3199 /* Write back the updated index. */
3200 if (write_index_entries(masterfd, idxbuf, idxbuf_pos) !=
3201 (ssize_t)sizeof(struct index_entry) * idxbuf_pos)
3202 {
3203 logf("write fail");
3204 error = true;
3205 goto error_exit;
3206 }
3207 }
3208 logf("done");
3209 }
3210
3211 /**
3212 * Walk through the temporary file containing the new tags.
3213 */
3214 // build_normal_index(h, tmpfd, masterfd, idx);
3215 logf("updating new indices...");
3216 lseek(masterfd, masterfd_pos, SEEK_SET);
3217 lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET);
3218 lseek(fd, 0, SEEK_END);
3219 for (i = 0; i < h->entry_count && !USR_CANCEL; i += idxbuf_pos)
3220 {
3221 int j;
3222
3223 idxbuf_pos = MIN(h->entry_count - i, IDX_BUF_DEPTH);
3224 if (init)
3225 {
3226 memset(idxbuf, 0, sizeof(struct index_entry)*IDX_BUF_DEPTH);
3227 }
3228 else
3229 {
3230 int loc = lseek(masterfd, 0, SEEK_CUR);
3231
3232 if (read_index_entries(masterfd, idxbuf, idxbuf_pos) !=
3233 (ssize_t)sizeof(struct index_entry) * idxbuf_pos)
3234 {
3235 logf("read fail #6");
3236 error = true;
3237 break ;
3238 }
3239 lseek(masterfd, loc, SEEK_SET);
3240 }
3241
3242 /* Read entry headers. */
3243 for (j = 0; j < idxbuf_pos; j++)
3244 {
3245 if (!TAGCACHE_IS_SORTED(index_type))
3246 {
3247 struct temp_file_entry entry;
3248 struct tagfile_entry fe;
3249
3250 if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) !=
3251 sizeof(struct temp_file_entry))
3252 {
3253 logf("read fail #7");
3254 error = true;
3255 break ;
3256 }
3257
3258 /* Read data. */
3259 if (entry.tag_length[index_type] >= build_idx_bufsz)
3260 {
3261 logf("too long entry!");
3262 logf("length=%d", entry.tag_length[index_type]);
3263 logf("pos=0x%02lx", (unsigned long) lseek(tmpfd, 0, SEEK_CUR));
3264 error = true;
3265 break ;
3266 }
3267
3268 lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR);
3269 if (read(tmpfd, build_idx_buf, entry.tag_length[index_type]) !=
3270 entry.tag_length[index_type])
3271 {
3272 logf("read fail #8");
3273 logf("offset=0x%02" PRIx32, entry.tag_offset[index_type]);
3274 logf("length=0x%02x", entry.tag_length[index_type]);
3275 error = true;
3276 break ;
3277 }
3278
3279 /* Write to index file. */
3280 idxbuf[j].tag_seek[index_type] = lseek(fd, 0, SEEK_CUR);
3281 fe.tag_length = entry.tag_length[index_type];
3282 fe.idx_id = tcmh.tch.entry_count + i + j;
3283 write_tagfile_entry(fd, &fe);
3284 write(fd, build_idx_buf, fe.tag_length);
3285 tempbufidx++;
3286
3287 /* Skip to next. */
3288 lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] -
3289 entry.tag_length[index_type], SEEK_CUR);
3290 }
3291 else
3292 {
3293 /* Locate the correct entry from the sorted array. */
3294 idxbuf[j].tag_seek[index_type] = tempbuf_find_location(i + j);
3295 if (idxbuf[j].tag_seek[index_type] < 0)
3296 {
3297 logf("entry not found (%d)", j);
3298 error = true;
3299 break ;
3300 }
3301 }
3302 }
3303
3304 /* Write index. */
3305 if (write_index_entries(masterfd, idxbuf, idxbuf_pos) !=
3306 (ssize_t)sizeof(struct index_entry) * idxbuf_pos)
3307 {
3308 logf("tagcache: write fail #4");
3309 error = true;
3310 break ;
3311 }
3312
3313 do_timed_yield();
3314 }
3315 logf("done");
3316
3317 /* Finally write the header. */
3318 tch.magic = TAGCACHE_MAGIC;
3319 tch.entry_count = tempbufidx;
3320 tch.datasize = lseek(fd, 0, SEEK_END) - sizeof(struct tagcache_header);
3321 lseek(fd, 0, SEEK_SET);
3322 write_tagcache_header(fd, &tch);
3323
3324 if (index_type != tag_filename)
3325 h->datasize += tch.datasize;
3326 logf("s:%d/%" PRId32 "/%" PRId32, index_type, tch.datasize, h->datasize);
3327 error_exit:
3328
3329 close(fd);
3330 close(masterfd);
3331
3332 if (error)
3333 return -2;
3334
3335 return 1;
3336}
3337
3338static bool commit(void)
3339{
3340 struct tagcache_header tch;
3341 struct master_header tcmh;
3342 int i, len, rc;
3343 int tmpfd;
3344 int masterfd;
3345#ifdef HAVE_DIRCACHE
3346 bool dircache_buffer_stolen = false;
3347#endif
3348#ifdef HAVE_TC_RAMCACHE
3349 bool ramcache_buffer_stolen = false;
3350#endif
3351 logf("committing tagcache");
3352
3353 while (write_lock)
3354 sleep(1);
3355
3356#if !defined(PLUGIN)
3357 int fd = open_db_fd(TAGCACHE_FILE_NOCOMMIT, O_RDONLY);
3358 if (fd >= 0)
3359 {
3360 logf("canceling commit");
3361 tc_stat.commit_delayed = true;
3362 close(fd);
3363 tmpfd = -1;
3364 }
3365 else
3366#endif /*!defined(PLUGIN)*/
3367 {
3368 tmpfd = open_db_fd(TAGCACHE_FILE_TEMP, O_RDONLY);
3369 }
3370 if (tmpfd < 0)
3371 {
3372 logf("nothing to commit");
3373 return true;
3374 }
3375
3376
3377 /* Load the header. */
3378 len = sizeof(struct tagcache_header);
3379 rc = read(tmpfd, &tch, len);
3380
3381 if (tch.magic != TAGCACHE_MAGIC || rc != len)
3382 {
3383 logf("incorrect tmpheader");
3384 close(tmpfd);
3385 remove_db_file(TAGCACHE_FILE_TEMP);
3386 return false;
3387 }
3388
3389 if (tch.entry_count == 0)
3390 logf("nothing to commit");
3391
3392 /* Fully initialize existing headers (if any) before going further. */
3393 tc_stat.ready = check_all_headers();
3394
3395#ifdef HAVE_EEPROM_SETTINGS
3396 remove_db_file(TAGCACHE_STATEFILE);
3397#endif
3398
3399 /* At first be sure to unload the ramcache! */
3400#ifdef HAVE_TC_RAMCACHE
3401 tc_stat.ramcache = false;
3402#endif
3403
3404 /* Beyond here, jump to commit_error to undo locks and restore dircache */
3405 rc = false;
3406 read_lock++;
3407
3408 /* Try to steal every buffer we can :) */
3409#ifdef HAVE_DIRCACHE
3410 if (tempbuf_size == 0)
3411 {
3412 /* Suspend dircache to free its allocation. */
3413 dircache_free_buffer();
3414 dircache_buffer_stolen = true;
3415
3416 allocate_tempbuf();
3417 }
3418#endif /* HAVE_DIRCACHE */
3419
3420#ifdef HAVE_TC_RAMCACHE
3421 if (tempbuf_size == 0 && tc_stat.ramcache_allocated > 0)
3422 {
3423 tcrc_buffer_lock();
3424 tempbuf = (char *)(tcramcache.hdr + 1);
3425 tempbuf_size = tc_stat.ramcache_allocated - sizeof(struct ramcache_header) - 128;
3426 tempbuf_size &= ~0x03;
3427 ramcache_buffer_stolen = true;
3428 }
3429#endif /* HAVE_TC_RAMCACHE */
3430
3431#if defined(PLUGIN)
3432 if (tempbuf_size == 0)
3433 {
3434 tempbuf = rb->plugin_get_audio_buffer(&tempbuf_size);
3435 tempbuf_size &= ~0x03;
3436 }
3437#endif /*defined(PLUGIN)*/
3438
3439 /* And finally fail if there are no buffers available. */
3440 if (tempbuf_size == 0)
3441 {
3442 logf("delaying commit until next boot");
3443 tc_stat.commit_delayed = true;
3444 close(tmpfd);
3445 goto commit_error;
3446 }
3447
3448 logf("commit %" PRId32 " entries...", tch.entry_count);
3449
3450 /* Mark DB dirty so it will stay disabled if commit fails. */
3451 current_tcmh.dirty = true;
3452 update_master_header();
3453
3454 /* Now create the index files. */
3455 tc_stat.commit_step = 0;
3456 tch.datasize = 0;
3457 tc_stat.commit_delayed = false;
3458
3459 for (i = 0; i < TAG_COUNT && !USR_CANCEL; i++)
3460 {
3461 int ret;
3462
3463 if (TAGCACHE_IS_NUMERIC(i))
3464 continue;
3465
3466 tc_stat.commit_step++;
3467 ret = build_index(i, &tch, tmpfd);
3468 if (ret <= 0)
3469 {
3470 close(tmpfd);
3471 logf("tagcache failed init");
3472 if (ret == 0)
3473 tc_stat.commit_delayed = true;
3474
3475 tc_stat.commit_step = 0;
3476 goto commit_error;
3477 }
3478 do_timed_yield();
3479 }
3480
3481 if (!build_numeric_indices(&tch, tmpfd))
3482 {
3483 logf("Failure to commit numeric indices");
3484 close(tmpfd);
3485 tc_stat.commit_step = 0;
3486 goto commit_error;
3487 }
3488
3489 close(tmpfd);
3490
3491 tc_stat.commit_step = 0;
3492
3493 if (!USR_CANCEL)
3494 {
3495 /* Update the master index headers. */
3496 if ( (masterfd = open_master_fd(&tcmh, true)) < 0)
3497 goto commit_error;
3498
3499 remove_db_file(TAGCACHE_FILE_TEMP);
3500
3501 tcmh.tch.entry_count += tch.entry_count;
3502 tcmh.tch.datasize = sizeof(struct master_header)
3503 + sizeof(struct index_entry) * tcmh.tch.entry_count
3504 + tch.datasize;
3505 tcmh.dirty = false;
3506 tcmh.commitid++;
3507
3508 lseek(masterfd, 0, SEEK_SET);
3509 write_master_header(masterfd, &tcmh);
3510 close(masterfd);
3511
3512 logf("tagcache committed");
3513 tagcache_commit_finalize();
3514
3515#if defined(HAVE_TC_RAMCACHE)
3516 if (ramcache_buffer_stolen)
3517 {
3518 tempbuf = NULL;
3519 tempbuf_size = 0;
3520 ramcache_buffer_stolen = false;
3521 tcrc_buffer_unlock();
3522 }
3523
3524 /* Reload tagcache. */
3525 if (tc_stat.ramcache_allocated > 0)
3526 tagcache_start_scan();
3527#endif /* HAVE_TC_RAMCACHE */
3528
3529 rc = true;
3530 } /*!USR_CANCEL*/
3531
3532commit_error:
3533#ifdef HAVE_TC_RAMCACHE
3534 if (ramcache_buffer_stolen)
3535 {
3536 tempbuf = NULL;
3537 tempbuf_size = 0;
3538 tcrc_buffer_unlock();
3539 }
3540#endif /* HAVE_TC_RAMCACHE */
3541
3542 read_lock--;
3543
3544#ifdef HAVE_DIRCACHE
3545 /* Resume the dircache, if we stole the buffer. */
3546 if (dircache_buffer_stolen)
3547 {
3548 free_tempbuf();
3549 dircache_resume();
3550 }
3551#endif /* HAVE_DIRCACHE */
3552
3553 return rc;
3554}
3555
3556void tagcache_commit_finalize(void)
3557{
3558 tc_stat.ready = check_all_headers();
3559 tc_stat.readyvalid = true;
3560}
3561
3562#if !defined(PLUGIN)
3563#ifndef __PCTOOL__
3564
3565static bool modify_numeric_entry(int masterfd, int idx_id, int tag, long data)
3566{
3567 struct index_entry idx;
3568
3569 if (!tc_stat.ready)
3570 return false;
3571
3572 if (!TAGCACHE_IS_NUMERIC(tag))
3573 return false;
3574
3575 if (!get_index(masterfd, idx_id, &idx, false))
3576 return false;
3577
3578 idx.tag_seek[tag] = data;
3579 idx.flag |= FLAG_DIRTYNUM;
3580
3581 return write_index(masterfd, idx_id, &idx);
3582}
3583
3584#if 0
3585bool tagcache_modify_numeric_entry(struct tagcache_search *tcs,
3586 int tag, long data)
3587{
3588 struct master_header myhdr;
3589
3590 if (tcs->masterfd < 0)
3591 {
3592 if ( (tcs->masterfd = open_master_fd(&myhdr, true)) < 0)
3593 return false;
3594 }
3595
3596 return modify_numeric_entry(tcs->masterfd, tcs->idx_id, tag, data);
3597}
3598#endif
3599
3600static bool command_queue_is_full(void)
3601{
3602 int next;
3603
3604 next = command_queue_widx + 1;
3605 if (next >= TAGCACHE_COMMAND_QUEUE_LENGTH)
3606 next = 0;
3607
3608 return (next == command_queue_ridx);
3609}
3610
3611static void command_queue_sync_callback(void)
3612{
3613 struct master_header myhdr;
3614 int masterfd;
3615
3616 mutex_lock(&command_queue_mutex);
3617
3618 if ( (masterfd = open_master_fd(&myhdr, true)) < 0)
3619 return;
3620
3621 while (command_queue_ridx != command_queue_widx)
3622 {
3623 struct tagcache_command_entry *ce = &command_queue[command_queue_ridx];
3624
3625 switch (ce->command)
3626 {
3627 case CMD_UPDATE_MASTER_HEADER:
3628 {
3629 close(masterfd);
3630 update_master_header();
3631
3632 /* Re-open the masterfd. */
3633 if ( (masterfd = open_master_fd(&myhdr, true)) < 0)
3634 return;
3635
3636 break;
3637 }
3638 case CMD_UPDATE_NUMERIC:
3639 {
3640 modify_numeric_entry(masterfd, ce->idx_id, ce->tag, ce->data);
3641 break;
3642 }
3643 }
3644
3645 if (++command_queue_ridx >= TAGCACHE_COMMAND_QUEUE_LENGTH)
3646 command_queue_ridx = 0;
3647 }
3648
3649 close(masterfd);
3650
3651 tc_stat.queue_length = 0;
3652 mutex_unlock(&command_queue_mutex);
3653}
3654
3655static void run_command_queue(bool force)
3656{
3657 if (COMMAND_QUEUE_IS_EMPTY)
3658 return;
3659
3660 if (force || command_queue_is_full())
3661 command_queue_sync_callback();
3662 else
3663 register_storage_idle_func(command_queue_sync_callback);
3664}
3665
3666static void queue_command(int cmd, long idx_id, int tag, long data)
3667{
3668 while (1)
3669 {
3670 int next;
3671
3672 mutex_lock(&command_queue_mutex);
3673 next = command_queue_widx + 1;
3674 if (next >= TAGCACHE_COMMAND_QUEUE_LENGTH)
3675 next = 0;
3676
3677 /* Make sure queue is not full. */
3678 if (next != command_queue_ridx)
3679 {
3680 struct tagcache_command_entry *ce = &command_queue[command_queue_widx];
3681
3682 ce->command = cmd;
3683 ce->idx_id = idx_id;
3684 ce->tag = tag;
3685 ce->data = data;
3686
3687 command_queue_widx = next;
3688
3689 tc_stat.queue_length++;
3690
3691 mutex_unlock(&command_queue_mutex);
3692 break;
3693 }
3694
3695 /* Queue is full, try again later... */
3696 mutex_unlock(&command_queue_mutex);
3697 sleep(1);
3698 }
3699}
3700
3701long tagcache_increase_serial(void)
3702{
3703 long old;
3704
3705 if (!tc_stat.ready)
3706 return -2;
3707
3708 while (read_lock)
3709 sleep(1);
3710
3711 old = current_tcmh.serial++;
3712 queue_command(CMD_UPDATE_MASTER_HEADER, 0, 0, 0);
3713
3714 return old;
3715}
3716
3717void tagcache_update_numeric(int idx_id, int tag, long data)
3718{
3719 queue_command(CMD_UPDATE_NUMERIC, idx_id, tag, data);
3720}
3721#endif /* !__PCTOOL__ */
3722
3723static bool write_tag(int fd, const char *tagstr, const char *datastr)
3724{
3725 char buf[512];
3726 const int bufsz = sizeof(buf);
3727 int i;
3728
3729 snprintf(buf, bufsz, "%s=\"", tagstr);
3730
3731 for (i = strlen(buf); i < (long)sizeof(buf)-4; i++)
3732 {
3733 if (*datastr == '\0')
3734 break;
3735
3736 if (*datastr == '"' || *datastr == '\\')
3737 buf[i++] = '\\';
3738
3739 else if (*datastr == '\n')
3740 {
3741 buf[i++] = '\\';
3742 buf[i] = 'n';
3743 continue;
3744 }
3745
3746 buf[i] = *(datastr++);
3747 }
3748
3749 str_setlen(buf, bufsz - 1);
3750 strmemccpy(&buf[i], "\" ", (bufsz - i - 1));
3751
3752 write(fd, buf, i + 2);
3753
3754 return true;
3755}
3756
3757#ifndef __PCTOOL__
3758
3759static bool read_tag(char *dest, long size,
3760 const char *src, const char *tagstr)
3761{
3762 int pos;
3763 char current_tag[32];
3764
3765 while (*src != '\0')
3766 {
3767 /* Skip all whitespace */
3768 while (*src == ' ')
3769 src++;
3770
3771 if (*src == '\0')
3772 break;
3773
3774 pos = 0;
3775 /* Read in tag name */
3776 while (*src != '=' && *src != ' ')
3777 {
3778 current_tag[pos] = *src;
3779 src++;
3780 pos++;
3781
3782 if (*src == '\0' || pos >= (int) sizeof(current_tag))
3783 return false;
3784 }
3785
3786 str_setlen(current_tag, pos);
3787
3788 /* Read in tag data */
3789
3790 /* Find the start. */
3791 while (*src != '"' && *src != '\0')
3792 src++;
3793
3794 if (*src == '\0' || *(++src) == '\0')
3795 return false;
3796
3797 /* Read the data. */
3798 for (pos = 0; pos < size; pos++)
3799 {
3800 if (*src == '\0')
3801 break;
3802
3803 if (*src == '\\')
3804 {
3805 src++;
3806 if (*src == 'n')
3807 dest[pos] = '\n';
3808 else
3809 dest[pos] = *src;
3810
3811 src++;
3812 continue;
3813 }
3814
3815 if (*src == '\0')
3816 break;
3817
3818 if (*src == '"')
3819 {
3820 src++;
3821 break;
3822 }
3823
3824 dest[pos] = *(src++);
3825 }
3826
3827 str_setlen(dest, pos);
3828
3829 if (!strcasecmp(tagstr, current_tag))
3830 return true;
3831 }
3832
3833 return false;
3834}
3835
3836static int parse_changelog_line(int line_n, char *buf, void *parameters)
3837{
3838 struct index_entry idx;
3839 char tag_data[TAGCACHE_BUFSZ];
3840 int idx_id;
3841 long masterfd = (long)(intptr_t)parameters;
3842 const int import_tags[] = { tag_playcount, tag_rating, tag_playtime,
3843 tag_lastplayed, tag_commitid, tag_lastelapsed,
3844 tag_lastoffset };
3845 int i;
3846 (void)line_n;
3847
3848 if (*buf == '#')
3849 return 0;
3850
3851 /* logf("%d/%s", line_n, buf); */
3852 if (!read_tag(tag_data, sizeof tag_data, buf, "filename"))
3853 {
3854 logf("%d/filename missing", line_n);
3855 logf("-> %s", buf);
3856 return 0;
3857 }
3858
3859 idx_id = find_index(tag_data);
3860 if (idx_id < 0)
3861 {
3862 logf("%d/entry not found", line_n);
3863 return 0;
3864 }
3865
3866 if (!get_index(masterfd, idx_id, &idx, false))
3867 {
3868 logf("%d/failed to retrieve index entry", line_n);
3869 return 0;
3870 }
3871
3872 /* Stop if tag has already been modified. */
3873 if (idx.flag & FLAG_DIRTYNUM)
3874 return 0;
3875
3876 logf("%d/import: %s", line_n, tag_data);
3877
3878 idx.flag |= FLAG_DIRTYNUM;
3879 for (i = 0; i < (long)(sizeof(import_tags)/sizeof(import_tags[0])); i++)
3880 {
3881 int data;
3882
3883 if (!read_tag(tag_data, sizeof tag_data, buf,
3884 tagcache_tag_to_str(import_tags[i])))
3885 {
3886 continue;
3887 }
3888
3889 data = atoi(tag_data);
3890 if (data < 0)
3891 continue;
3892
3893 idx.tag_seek[import_tags[i]] = data;
3894
3895 if (import_tags[i] == tag_lastplayed && data >= current_tcmh.serial)
3896 current_tcmh.serial = data + 1;
3897 else if (import_tags[i] == tag_commitid && data >= current_tcmh.commitid)
3898 current_tcmh.commitid = data + 1;
3899 }
3900
3901 return write_index(masterfd, idx_id, &idx) ? 0 : -5;
3902}
3903
3904bool tagcache_import_changelog(void)
3905{
3906 struct master_header myhdr;
3907 struct tagcache_header tch;
3908 int clfd;
3909 long masterfd;
3910 char buf[2048];
3911 const int bufsz = sizeof(buf);
3912
3913 if (!tc_stat.ready)
3914 return false;
3915
3916 while (read_lock)
3917 sleep(1);
3918
3919 clfd = open_db_fd(TAGCACHE_FILE_CHANGELOG, O_RDONLY);
3920 if (clfd < 0)
3921 {
3922 logf("failure to open changelog");
3923 return false;
3924 }
3925
3926 if ( (masterfd = open_master_fd(&myhdr, true)) < 0)
3927 {
3928 close(clfd);
3929 return false;
3930 }
3931
3932 write_lock++;
3933
3934 filenametag_fd = open_tag_fd(&tch, tag_filename, false);
3935
3936 fast_readline(clfd, buf, bufsz, (void *)(intptr_t)masterfd,
3937 parse_changelog_line);
3938
3939 close(clfd);
3940 close(masterfd);
3941
3942 if (filenametag_fd >= 0)
3943 {
3944 close(filenametag_fd);
3945 filenametag_fd = -1;
3946 }
3947
3948 write_lock--;
3949
3950 update_master_header();
3951
3952 return true;
3953}
3954
3955#endif /* !__PCTOOL__ */
3956
3957bool tagcache_create_changelog(struct tagcache_search *tcs)
3958{
3959 struct master_header myhdr;
3960 struct index_entry idx;
3961 char buf[TAGCACHE_BUFSZ];
3962 const int bufsz = sizeof(buf);
3963 char temp[32];
3964 int clfd;
3965 int i, j;
3966
3967 if (!tc_stat.ready)
3968 return false;
3969
3970 if (!tagcache_search(tcs, tag_filename))
3971 return false;
3972
3973 /* Initialize the changelog */
3974 clfd = open_db_fd(TAGCACHE_FILE_CHANGELOG, O_WRONLY | O_CREAT | O_TRUNC);
3975 if (clfd < 0)
3976 {
3977 logf("failure to open changelog");
3978 tagcache_search_finish(tcs);
3979 return false;
3980 }
3981
3982 if (tcs->masterfd < 0)
3983 {
3984 if ( (tcs->masterfd = open_master_fd(&myhdr, false)) < 0)
3985 {
3986 close(clfd);
3987 tagcache_search_finish(tcs);
3988 return false;
3989 }
3990 }
3991 else
3992 {
3993 lseek(tcs->masterfd, 0, SEEK_SET);
3994 read_master_header(tcs->masterfd, &myhdr);
3995 }
3996
3997 write(clfd, "## Changelog version 1\n", 23);
3998
3999 for (i = 0; i < myhdr.tch.entry_count; i++)
4000 {
4001 if (read_index_entries(tcs->masterfd, &idx, 1) != sizeof(struct index_entry))
4002 {
4003 logf("read error #9");
4004 tagcache_search_finish(tcs);
4005 close(clfd);
4006 return false;
4007 }
4008
4009 /* Skip until the entry found has been modified. */
4010 if (! (idx.flag & FLAG_DIRTYNUM) )
4011 continue;
4012
4013 /* Skip deleted entries too. */
4014 if (idx.flag & FLAG_DELETED)
4015 continue;
4016
4017 /* Now retrieve all tags. */
4018 for (j = 0; j < TAG_COUNT; j++)
4019 {
4020 if (TAGCACHE_IS_NUMERIC(j))
4021 {
4022 itoa_buf(temp, sizeof temp, (int)idx.tag_seek[j]);
4023 write_tag(clfd, tagcache_tag_to_str(j), temp);
4024 continue;
4025 }
4026
4027 tcs->type = j;
4028 tagcache_retrieve(tcs, i, tcs->type, buf, bufsz);
4029 write_tag(clfd, tagcache_tag_to_str(j), buf);
4030 }
4031
4032 write(clfd, "\n", 1);
4033 do_timed_yield();
4034 }
4035
4036 close(clfd);
4037
4038 tagcache_search_finish(tcs);
4039
4040 return true;
4041}
4042
4043static bool delete_entry(long idx_id)
4044{
4045 int fd = -1;
4046 int masterfd = -1;
4047 int tag, i;
4048 struct index_entry idx, myidx;
4049 struct master_header myhdr;
4050 int in_use[TAG_COUNT];
4051
4052 logf("delete_entry(): %ld", idx_id);
4053
4054#ifdef HAVE_TC_RAMCACHE
4055 /* At first mark the entry removed from ram cache. */
4056 if (tc_stat.ramcache)
4057 tcramcache.hdr->indices[idx_id].flag |= FLAG_DELETED;
4058#endif
4059
4060 if ( (masterfd = open_master_fd(&myhdr, true) ) < 0)
4061 return false;
4062
4063 lseek(masterfd, idx_id * sizeof(struct index_entry), SEEK_CUR);
4064 if (read_index_entries(masterfd, &myidx, 1) != sizeof(struct index_entry))
4065 {
4066 logf("delete_entry(): read error");
4067 goto cleanup;
4068 }
4069
4070 if (myidx.flag & FLAG_DELETED)
4071 {
4072 logf("delete_entry(): already deleted!");
4073 goto cleanup;
4074 }
4075
4076 myidx.flag |= FLAG_DELETED;
4077 lseek(masterfd, -(off_t)sizeof(struct index_entry), SEEK_CUR);
4078 if (write_index_entries(masterfd, &myidx, 1) != sizeof(struct index_entry))
4079 {
4080 logf("delete_entry(): write_error #1");
4081 goto cleanup;
4082 }
4083
4084 /* Now check which tags are no longer in use (if any) */
4085 for (tag = 0; tag < TAG_COUNT; tag++)
4086 in_use[tag] = 0;
4087
4088 lseek(masterfd, sizeof(struct master_header), SEEK_SET);
4089 for (i = 0; i < myhdr.tch.entry_count; i++)
4090 {
4091 struct index_entry *idxp;
4092
4093#ifdef HAVE_TC_RAMCACHE
4094 /* Use RAM DB if available for greater speed */
4095 if (tc_stat.ramcache)
4096 idxp = &tcramcache.hdr->indices[i];
4097 else
4098#endif
4099 {
4100 if (read_index_entries(masterfd, &idx, 1) != sizeof(struct index_entry))
4101 {
4102 logf("delete_entry(): read error #2");
4103 goto cleanup;
4104 }
4105 idxp = &idx;
4106 }
4107
4108 if (idxp->flag & FLAG_DELETED)
4109 continue;
4110
4111 for (tag = 0; tag < TAG_COUNT; tag++)
4112 {
4113 if (TAGCACHE_IS_NUMERIC(tag))
4114 continue;
4115
4116 if (idxp->tag_seek[tag] == myidx.tag_seek[tag])
4117 in_use[tag]++;
4118 }
4119 }
4120
4121 /* Now delete all tags no longer in use. */
4122 for (tag = 0; tag < TAG_COUNT; tag++)
4123 {
4124 struct tagcache_header tch;
4125 int oldseek = myidx.tag_seek[tag];
4126
4127 if (TAGCACHE_IS_NUMERIC(tag))
4128 continue;
4129
4130 /**
4131 * Replace tag seek with a hash value of the field string data.
4132 * That way runtime statistics of moved or altered files can be
4133 * resurrected.
4134 */
4135#ifdef HAVE_TC_RAMCACHE
4136 if (tc_stat.ramcache && tag != tag_filename)
4137 {
4138 struct tagfile_entry *tfe;
4139 int32_t *seek = &tcramcache.hdr->indices[idx_id].tag_seek[tag];
4140
4141 /* crc_32 is assumed not to yield (why would it...?) */
4142 tfe = (struct tagfile_entry *)&tcramcache.hdr->tags[tag][*seek];
4143 *seek = crc_32(tfe->tag_data, strlen(tfe->tag_data), 0xffffffff);
4144 myidx.tag_seek[tag] = *seek;
4145 }
4146 else
4147#endif /* HAVE_TC_RAMCACHE */
4148 {
4149 struct tagfile_entry tfe;
4150
4151 /* Open the index file, which contains the tag names. */
4152 if ((fd = open_tag_fd(&tch, tag, true)) < 0)
4153 goto cleanup;
4154
4155 /* Skip the header block */
4156 lseek(fd, myidx.tag_seek[tag], SEEK_SET);
4157
4158 switch (read_tagfile_entry_and_tag(fd, &tfe,
4159 build_idx_buf, build_idx_bufsz))
4160 {
4161 case e_SUCCESS_LEN_ZERO:
4162 logf("deleted_entry(): SUCCESS");
4163 /* FALL THROUGH */
4164 case e_SUCCESS:
4165 break;
4166 case e_ENTRY_SIZEMISMATCH:
4167 logf("delete_entry(): read error #3");
4168 goto cleanup;
4169 case e_TAG_TOOLONG:
4170 logf("too long tag #4");
4171 goto cleanup;
4172 case e_TAG_SIZEMISMATCH:
4173 logf("delete_entry(): read error #3");
4174 goto cleanup;
4175 }
4176
4177 myidx.tag_seek[tag] = crc_32(build_idx_buf,
4178 strlen(build_idx_buf), 0xffffffff);
4179 }
4180
4181 if (in_use[tag])
4182 {
4183 logf("in use: %d/%d", tag, in_use[tag]);
4184 if (fd >= 0)
4185 {
4186 close(fd);
4187 fd = -1;
4188 }
4189 continue;
4190 }
4191
4192#ifdef HAVE_TC_RAMCACHE
4193 /* Delete from ram. */
4194 if (tc_stat.ramcache && tag != tag_filename)
4195 {
4196 struct tagfile_entry *tagentry =
4197 (struct tagfile_entry *)&tcramcache.hdr->tags[tag][oldseek];
4198 str_setlen(tagentry->tag_data, 0);
4199 }
4200#endif /* HAVE_TC_RAMCACHE */
4201
4202 /* Open the index file, which contains the tag names. */
4203 if (fd < 0)
4204 {
4205 if ((fd = open_tag_fd(&tch, tag, true)) < 0)
4206 goto cleanup;
4207 }
4208
4209 /* Skip the header block */
4210 lseek(fd, oldseek + sizeof(struct tagfile_entry), SEEK_SET);
4211
4212 /* Debug, print 10 first characters of the tag
4213 read(fd, buf, 10);
4214 buf[10]='\0';
4215 logf("TAG:%s", buf);
4216 lseek(fd, -10, SEEK_CUR);
4217 */
4218
4219 /* Write first data byte in tag as \0 */
4220 write(fd, "", 1);
4221
4222 /* Now tag data has been removed */
4223 close(fd);
4224 fd = -1;
4225 }
4226
4227 /* Write index entry back into master index. */
4228 lseek(masterfd, sizeof(struct master_header) +
4229 (idx_id * sizeof(struct index_entry)), SEEK_SET);
4230 if (write_index_entries(masterfd, &myidx, 1) != sizeof(struct index_entry))
4231 {
4232 logf("delete_entry(): write_error #2");
4233 goto cleanup;
4234 }
4235
4236 close(masterfd);
4237
4238 return true;
4239
4240 cleanup:
4241 if (fd >= 0)
4242 close(fd);
4243 if (masterfd >= 0)
4244 close(masterfd);
4245
4246 return false;
4247}
4248
4249/**
4250 * Returns true if there is an event waiting in the queue
4251 * that requires the current operation to be aborted.
4252 */
4253static bool check_event_queue(void)
4254{
4255#ifndef __PCTOOL__
4256 struct queue_event ev;
4257
4258 if(!queue_peek(&tagcache_queue, &ev))
4259 return false;
4260
4261 switch (ev.id)
4262 {
4263 case Q_STOP_SCAN:
4264 case SYS_POWEROFF:
4265 case SYS_REBOOT:
4266 case SYS_USB_CONNECTED:
4267 return true;
4268 }
4269#endif /* __PCTOOL__ */
4270
4271 return false;
4272}
4273
4274#ifdef HAVE_TC_RAMCACHE
4275
4276static void fix_ramcache(void* old_addr, void* new_addr)
4277{
4278 ptrdiff_t offpos = new_addr - old_addr;
4279 for (int i = 0; i < TAG_COUNT; i++)
4280 tcramcache.hdr->tags[i] += offpos;
4281}
4282
4283static int move_cb(int handle, void* current, void* new)
4284{
4285 (void)handle;
4286 fix_ramcache(current, new);
4287 tcramcache.hdr = new;
4288 return BUFLIB_CB_OK;
4289}
4290
4291static struct buflib_callbacks ops = {
4292 .move_callback = move_cb,
4293 .shrink_callback = NULL,
4294};
4295
4296static bool allocate_tagcache(void)
4297{
4298 tc_stat.ramcache_allocated = 0;
4299 tcramcache.handle = 0;
4300 tcramcache.hdr = NULL;
4301
4302 /* Load the header. */
4303 struct master_header tcmh;
4304 int fd = open_master_fd(&tcmh, false);
4305 if (fd < 0)
4306 return false;
4307
4308 close(fd);
4309
4310 /**
4311 * Now calculate the required cache size plus
4312 * some extra space for alignment fixes.
4313 */
4314 size_t alloc_size = tcmh.tch.datasize + 256 + TAGCACHE_RESERVE +
4315 sizeof(struct ramcache_header) + TAG_COUNT*sizeof(void *);
4316#ifdef HAVE_DIRCACHE
4317 alloc_size += tcmh.tch.entry_count*sizeof(struct dircache_fileref);
4318#endif
4319
4320 int handle = core_alloc_ex(alloc_size, &ops);
4321 if (handle <= 0)
4322 return false;
4323
4324 tcramcache.handle = handle;
4325 tcramcache.hdr = core_get_data(handle);
4326 tc_stat.ramcache_allocated = alloc_size;
4327
4328 memset(tcramcache.hdr, 0, sizeof(struct ramcache_header));
4329 memcpy(¤t_tcmh, &tcmh, sizeof current_tcmh);
4330 logf("tagcache: %d bytes allocated.", tc_stat.ramcache_allocated);
4331
4332 return true;
4333}
4334
4335#ifdef HAVE_EEPROM_SETTINGS
4336static bool tagcache_dumpload(void)
4337{
4338 struct statefile_header shdr;
4339 int fd, rc, handle;
4340
4341 tcramcache.handle = 0;
4342 tcramcache.hdr = NULL;
4343
4344 fd = open_db_fd(TAGCACHE_STATEFILE, O_RDONLY);
4345 if (fd < 0)
4346 {
4347 logf("no tagcache statedump");
4348 return false;
4349 }
4350
4351 /* Check the statefile memory placement */
4352 rc = read(fd, &shdr, sizeof(struct statefile_header));
4353 if (rc != sizeof(struct statefile_header)
4354 || shdr.magic != TAGCACHE_STATEFILE_MAGIC
4355 || shdr.mh.tch.magic != TAGCACHE_MAGIC)
4356 {
4357 logf("incorrect statefile");
4358 close(fd);
4359 return false;
4360 }
4361
4362 /* Lets allocate real memory and load it */
4363 handle = core_alloc_ex(shdr.tc_stat.ramcache_allocated, &ops);
4364 if (handle <= 0)
4365 {
4366 logf("alloc failure");
4367 return false;
4368 }
4369
4370 tcramcache.handle = handle;
4371 tcrc_buffer_lock();
4372 tcramcache.hdr = core_get_data(handle);
4373 rc = read(fd, tcramcache.hdr, shdr.tc_stat.ramcache_allocated);
4374 tcrc_buffer_unlock();
4375
4376 close(fd);
4377
4378 if (rc != shdr.tc_stat.ramcache_allocated)
4379 {
4380 logf("read failure!");
4381 core_free(handle);
4382 return false;
4383 }
4384
4385 tc_stat = shdr.tc_stat;
4386
4387 /* Now fix the pointers */
4388 fix_ramcache(shdr.hdr, tcramcache.hdr);
4389
4390 /* Load the tagcache master header (should match the actual DB file header). */
4391 memcpy(¤t_tcmh, &shdr.mh, sizeof current_tcmh);
4392
4393 return true;
4394}
4395
4396static bool tagcache_dumpsave(void)
4397{
4398 struct statefile_header shdr;
4399 int fd;
4400
4401 if (!tc_stat.ramcache)
4402 return false;
4403
4404 fd = open_db_fd(TAGCACHE_STATEFILE, O_WRONLY | O_CREAT | O_TRUNC);
4405 if (fd < 0)
4406 {
4407 logf("failed to create a statedump");
4408 return false;
4409 }
4410
4411 /* Create the header */
4412 shdr.magic = TAGCACHE_STATEFILE_MAGIC;
4413 shdr.hdr = tcramcache.hdr;
4414 memcpy(&shdr.mh, ¤t_tcmh, sizeof current_tcmh);
4415 memcpy(&shdr.tc_stat, &tc_stat, sizeof tc_stat);
4416 write(fd, &shdr, sizeof shdr);
4417
4418 /* And dump the data too */
4419 tcrc_buffer_lock();
4420 write(fd, tcramcache.hdr, tc_stat.ramcache_allocated);
4421 tcrc_buffer_unlock();
4422 close(fd);
4423
4424 return true;
4425}
4426#endif /* HAVE_EEPROM_SETTINGS */
4427
4428static bool load_tagcache(void)
4429{
4430 /* DEBUG: After tagcache commit and dircache rebuild, hdr-sturcture
4431 * may become corrupt. */
4432
4433 bool ok = false;
4434 ssize_t bytesleft = tc_stat.ramcache_allocated - sizeof(struct ramcache_header);
4435 int fd;
4436
4437#ifdef HAVE_DIRCACHE
4438 /* Wait for any in-progress dircache build to complete */
4439 dircache_wait();
4440#endif /* HAVE_DIRCACHE */
4441
4442 logf("loading tagcache to ram...");
4443
4444 tcrc_buffer_lock(); /* lock for the rest of the scan, simpler to handle */
4445
4446 fd = open_db_fd(TAGCACHE_FILE_MASTER, O_RDONLY);
4447 if (fd < 0)
4448 {
4449 logf("tagcache open failed");
4450 goto failure;
4451 }
4452
4453 struct master_header tcmh;
4454 if (read_master_header(fd, &tcmh) != sizeof(struct master_header) ||
4455 tcmh.tch.magic != TAGCACHE_MAGIC)
4456 {
4457 logf("incorrect header");
4458 goto failure;
4459 }
4460
4461 /* Master header copy should already match, this can be redundant to do. */
4462 current_tcmh = tcmh;
4463
4464 /* Load the master index table. */
4465 for (int i = 0; i < tcmh.tch.entry_count; i++)
4466 {
4467 bytesleft -= sizeof(struct index_entry);
4468 if (bytesleft < 0)
4469 {
4470 logf("too big tagcache.");
4471 goto failure;
4472 }
4473
4474 int rc = read_index_entries(fd, &tcramcache.hdr->indices[i], 1);
4475 if (rc != sizeof (struct index_entry))
4476 {
4477 logf("read error #10");
4478 goto failure;
4479 }
4480 }
4481
4482 close(fd);
4483 fd = -1;
4484
4485 /* Load the tags right after the index entries */
4486 char *p = (char *)&tcramcache.hdr->indices[tcmh.tch.entry_count];
4487
4488 for (int tag = 0; tag < TAG_COUNT; tag++)
4489 {
4490 ssize_t rc;
4491
4492 if (TAGCACHE_IS_NUMERIC(tag))
4493 continue;
4494
4495 p = TC_ALIGN_PTR(p, struct tagcache_header, &rc);
4496 bytesleft -= rc;
4497 if (bytesleft < (ssize_t)sizeof(struct tagcache_header))
4498 {
4499 logf("Too big tagcache #10.5");
4500 goto failure;
4501 }
4502
4503 tcramcache.hdr->tags[tag] = p;
4504
4505 /* Load the header */
4506 struct tagcache_header *tch = (struct tagcache_header *)p;
4507 p += sizeof(struct tagcache_header);
4508 bytesleft -= sizeof (struct tagcache_header);
4509
4510 fd = open_tag_fd(tch, tag, false);
4511 if (rc < 0)
4512 goto failure;
4513
4514 /* Load the entries for this tag */
4515 for (tcramcache.hdr->entry_count[tag] = 0;
4516 tcramcache.hdr->entry_count[tag] < tch->entry_count;
4517 tcramcache.hdr->entry_count[tag]++)
4518 {
4519 /* Abort if we got a critical event in queue */
4520 if (do_timed_yield() && check_event_queue())
4521 goto failure;
4522
4523 p = TC_ALIGN_PTR(p, struct tagfile_entry, &rc);
4524 bytesleft -= rc;
4525 if (bytesleft < (ssize_t)sizeof(struct tagfile_entry))
4526 {
4527 logf("Too big tagcache #10.75");
4528 goto failure;
4529 }
4530
4531 struct tagfile_entry *fe = (struct tagfile_entry *)p;
4532 off_t pos = lseek(fd, 0, SEEK_CUR);
4533
4534 /* Load the header for the tag itself */
4535 if (read_tagfile_entry(fd, fe) != sizeof(struct tagfile_entry))
4536 {
4537 /* End of lookup table. */
4538 logf("read error #11");
4539 goto failure;
4540 }
4541
4542 int idx_id = fe->idx_id; /* dircache reference clobbers *fe */
4543 struct index_entry *idx = &tcramcache.hdr->indices[idx_id];
4544
4545 if (idx_id != -1 || tag == tag_filename) /* filename NOT optional */
4546 {
4547 if (idx_id < 0 || idx_id >= tcmh.tch.entry_count)
4548 {
4549 logf("corrupt tagfile entry:tag=%d:idxid=%d", tag, idx_id);
4550 goto failure;
4551 }
4552
4553 if (idx->tag_seek[tag] != pos)
4554 {
4555 logf("corrupt data structures!:");
4556 logf(" tag_seek[%d]=%" PRId32 ":pos=%ld", tag,
4557 idx->tag_seek[tag], (long) pos);
4558 goto failure;
4559 }
4560 }
4561
4562 /* We have a special handling for the filename tags; neither the
4563 paths nor the entry headers are stored; only the tagcache header
4564 and dircache references are. */
4565 if (tag == tag_filename)
4566 {
4567 #ifdef HAVE_DIRCACHE
4568 if (idx->flag & FLAG_DIRCACHE)
4569 {
4570 /* This flag must not be used yet. */
4571 logf("internal error!");
4572 goto failure;
4573 }
4574
4575 p += sizeof (struct dircache_fileref);
4576 bytesleft -= sizeof (struct dircache_fileref);
4577 #endif /* HAVE_DIRCACHE */
4578
4579 char filename[TAGCACHE_BUFSZ];
4580 if (fe->tag_length >= (long)sizeof(filename)-1)
4581 {
4582 read(fd, filename, 10);
4583 str_setlen(filename, 10);
4584 logf("TAG:%s", filename);
4585 logf("too long filename");
4586 goto failure;
4587 }
4588
4589 if ((idx->flag & FLAG_DELETED)
4590 IFN_DIRCACHE( || !global_settings.tagcache_autoupdate ))
4591 {
4592 /* seek over tag data instead of reading */
4593 if (lseek(fd, fe->tag_length, SEEK_CUR) < 0)
4594 {
4595 logf("read error #11.5");
4596 goto failure;
4597 }
4598
4599 continue;
4600 }
4601
4602 if (read(fd, filename, fe->tag_length) != fe->tag_length)
4603 {
4604 logf("read error #12");
4605 goto failure;
4606 }
4607 continue;
4608 }
4609
4610 bytesleft -= sizeof(struct tagfile_entry) + fe->tag_length;
4611 if (bytesleft < 0)
4612 {
4613 logf("too big tagcache #2");
4614 logf("tl: %" PRId32, fe->tag_length);
4615 logf("bl: %ld", (long) bytesleft);
4616 goto failure;
4617 }
4618
4619 p = fe->tag_data;
4620 rc = read(fd, p, fe->tag_length);
4621 p += rc;
4622
4623 if (rc != fe->tag_length)
4624 {
4625 logf("read error #13");
4626 logf("rc=0x%04x", (unsigned int)rc); // 0x431
4627 logf("len=0x%04" PRIx32, fe->tag_length); // 0x4000
4628 logf("pos=0x%04lx", (unsigned long) lseek(fd, 0, SEEK_CUR)); // 0x433
4629 logf("tag=0x%02x", tag); // 0x00
4630 goto failure;
4631 }
4632 }
4633
4634 #ifdef HAVE_DIRCACHE
4635 if (tag == tag_filename)
4636 p = (char *)&tcrc_dcfrefs[tcmh.tch.entry_count];
4637 #endif /* HAVE_DIRCACHE */
4638
4639 close(fd);
4640 }
4641
4642 tc_stat.ramcache_used = tc_stat.ramcache_allocated - bytesleft;
4643 logf("tagcache loaded into ram!");
4644 logf("utilization: %d%%", 100*tc_stat.ramcache_used / tc_stat.ramcache_allocated);
4645
4646 ok = true;
4647
4648failure:
4649 if (fd >= 0)
4650 close(fd);
4651
4652 tcrc_buffer_unlock();
4653 return ok;
4654}
4655#endif /* HAVE_TC_RAMCACHE */
4656
4657static bool check_file_refs(bool auto_update)
4658{
4659 int fd;
4660 bool ret = true;
4661 char buf[TAGCACHE_BUFSZ];
4662 const int bufsz = sizeof(buf);
4663 struct tagfile_entry tfe;
4664 struct tagcache_header hdr;
4665
4666 logf("reverse scan...");
4667
4668#ifdef HAVE_DIRCACHE
4669 if (tcramcache.handle > 0)
4670 tcrc_buffer_lock();
4671 else
4672 return false;
4673 /* Wait for any in-progress dircache build to complete */
4674 dircache_wait();
4675#else
4676 if (!auto_update)
4677 return false;
4678#endif
4679
4680 fd = open_tag_fd(&hdr, tag_filename, false); /* open read only*/
4681
4682 if (fd < 0)
4683 {
4684 logf(TAGCACHE_FILE_INDEX " open fail", tag_filename);
4685 return false;
4686 }
4687
4688 processed_dir_count = 0;
4689
4690 while (!check_event_queue())
4691 {
4692 int res = read_tagfile_entry_and_tag(fd, &tfe, buf, bufsz);
4693 processed_dir_count++;
4694
4695 switch (res)
4696 {
4697 case e_ENTRY_SIZEMISMATCH:
4698 logf("size mismatch entry EOF?"); /* likely EOF */
4699 ret = false;
4700 goto wend_finished;
4701 case e_TAG_TOOLONG:
4702 logf("too long tag");
4703 ret = false;
4704 goto wend_finished;
4705 case e_TAG_SIZEMISMATCH:
4706 logf("size mismatch tag - read error #14");
4707 ret = false;
4708 goto wend_finished;
4709 case e_SUCCESS:
4710 break;
4711 case e_SUCCESS_LEN_ZERO:
4712 continue;
4713 }
4714
4715 int idx_id = tfe.idx_id; /* dircache reference clobbers *tfe */
4716#ifdef HAVE_DIRCACHE
4717 struct index_entry *idx = &tcramcache.hdr->indices[idx_id];
4718 unsigned int searchflag;
4719 if (!auto_update)
4720 {
4721 if(idx->flag & FLAG_DIRCACHE) /* already found */
4722 {
4723 continue;
4724 }
4725 searchflag = DCS_CACHED_PATH; /* attempt to load cache references */
4726 }
4727 else /* If auto updating, check storage too */
4728 {
4729 searchflag = DCS_STORAGE_PATH;
4730 }
4731
4732 int rc_cache = dircache_search(searchflag | DCS_UPDATE_FILEREF,
4733 &tcrc_dcfrefs[idx_id], buf);
4734
4735 if (rc_cache > 0) /* in cache and we have fileref */
4736 {
4737 idx->flag |= FLAG_DIRCACHE;
4738 }
4739 else if (rc_cache == 0) /* not in cache but okay */
4740 {;}
4741 else if (auto_update && rc_cache == ENOENT)
4742#else
4743 if (!file_exists(buf))
4744#endif /* HAVE_DIRCACHE */
4745 {
4746 logf("Entry no longer valid.");
4747 logf("-> %s / %" PRId32, buf, tfe.tag_length);
4748 delete_entry(idx_id);
4749 }
4750
4751 do_timed_yield();
4752 }
4753
4754wend_finished:
4755
4756#ifdef HAVE_DIRCACHE
4757 if (tcramcache.handle > 0)
4758 tcrc_buffer_unlock();
4759#endif
4760 close(fd);
4761 logf("done");
4762
4763 return ret;
4764}
4765
4766static bool check_deleted_files(void)
4767{
4768 return check_file_refs(true);
4769}
4770
4771/* Note that this function must not be inlined, otherwise the whole point
4772 * of having the code in a separate function is lost.
4773 */
4774static void NO_INLINE check_ignore(const char *dirname,
4775 int *ignore, int *unignore)
4776{
4777 char newpath[MAX_PATH];
4778 const int bufsz = sizeof(newpath);
4779
4780 /* check for a database.ignore file */
4781 snprintf(newpath, bufsz, "%s/database.ignore", dirname);
4782 *ignore = file_exists(newpath);
4783 /* check for a database.unignore file */
4784 snprintf(newpath, bufsz, "%s/database.unignore", dirname);
4785 *unignore = file_exists(newpath);
4786}
4787
4788/* max roots on native. on application more can be added via malloc() */
4789#define MAX_STATIC_ROOTS 12
4790
4791static struct search_roots_ll {
4792 const char *path;
4793 struct search_roots_ll * next;
4794} roots_ll[MAX_STATIC_ROOTS];
4795
4796/* check if the path is already included in the search roots, by the
4797 * means that the path itself or one of its parents folders is in the list */
4798static bool search_root_exists(const char *path)
4799{
4800 struct search_roots_ll *this;
4801 for(this = &roots_ll[0]; this; this = this->next)
4802 {
4803 size_t root_len = strlen(this->path);
4804 /* check if the link target is inside of an existing search root
4805 * don't add if target is inside, we'll scan it later */
4806 if (!strncmp(this->path, path, root_len))
4807 return true;
4808 }
4809 return false;
4810}
4811
4812#ifdef APPLICATION
4813/*
4814 * This adds a path to the search roots, possibly during traveling through
4815 * the filesystem. It only adds if the path is not inside an already existing
4816 * search root.
4817 *
4818 * Returns true if it added the path to the search roots
4819 *
4820 * Windows 2000 and greater supports symlinks, but they don't provide
4821 * realpath() or readlink(), and symlinks are rarely used on them so
4822 * ignore this for windows for now
4823 **/
4824static bool add_search_root(const char *name)
4825{
4826 (void)name;
4827#ifndef WIN32
4828 struct search_roots_ll *this, *prev = NULL;
4829 char target[MAX_PATH];
4830 const int target_bufsz = sizeof(target);
4831 /* Okay, realpath() is almost completely broken on android
4832 *
4833 * It doesn't accept NULL for resolved_name to dynamically allocate
4834 * the resulting path; and it assumes resolved_name to be PATH_MAX
4835 * (actually MAXPATHLEN, but it's the same [as of 2.3]) long
4836 * and blindly writes to the end if it
4837 *
4838 * therefore use sufficiently large static storage here
4839 * Note that PATH_MAX != MAX_PATH
4840 **/
4841 static char abs_target[PATH_MAX];
4842 ssize_t len;
4843
4844 len = readlink(name, target, target_bufsz-1);
4845 if (len < 0)
4846 return false;
4847
4848 str_setlen(target, len);
4849 if (realpath(target, abs_target) == NULL)
4850 return false;
4851
4852 if (search_root_exists(abs_target))
4853 return false;
4854
4855 /* get the end of the list */
4856 for(this = &roots_ll[0]; this; prev = this, this = this->next);
4857
4858 if (prev)
4859 {
4860 size_t len = strlen(abs_target) + 1; /* count \0 */
4861 this = malloc(sizeof(struct search_roots_ll) + len );
4862 if (!this || len > MIN(PATH_MAX, MAX_PATH))
4863 {
4864 logf("Error at adding a search root: %s", this ? "path too long":"OOM");
4865 free(this);
4866 prev->next = NULL;
4867 return false;
4868 }
4869 this->path = ((char*)this) + sizeof(struct search_roots_ll);
4870 strcpy((char*)this->path, abs_target); /* ok to cast const away here */
4871 this->next = NULL;
4872 prev->next = this;
4873 logf("Added %s to the search roots\n", abs_target);
4874 return true;
4875 }
4876#endif
4877 return false;
4878}
4879
4880static int free_search_root_single(struct search_roots_ll * start)
4881{
4882 if (start < &roots_ll[0] && start >= &roots_ll[MAX_STATIC_ROOTS])
4883 {
4884 free(start->next);
4885 return sizeof(struct search_roots_ll);
4886 }
4887 return 0;
4888}
4889
4890static int free_search_roots(struct search_roots_ll * start)
4891{
4892 int ret = 0;
4893 if (start->next)
4894 {
4895 ret += free_search_root_single(start->next);
4896 }
4897 return ret;
4898}
4899#else /* native, simulator */
4900#define add_search_root(a) do {} while(0)
4901#define free_search_roots(a) do {} while(0)
4902#endif
4903
4904static bool check_dir(const char *dirname, int add_files)
4905{
4906 int success = false;
4907
4908 DIR *dir = opendir(dirname);
4909 if (!dir)
4910 {
4911 logf("tagcache: opendir(%s) failed", dirname);
4912 return false;
4913 }
4914
4915 /* check for a database.ignore and database.unignore */
4916 int ignore, unignore;
4917 check_ignore(dirname, &ignore, &unignore);
4918
4919 /* don't do anything if both ignore and unignore are there */
4920 if (ignore != unignore)
4921 add_files = unignore;
4922
4923 /* Recursively scan the dir. */
4924 while (!check_event_queue())
4925 {
4926 struct dirent *entry = readdir(dir);
4927 if (entry == NULL)
4928 {
4929 success = true;
4930 break;
4931 }
4932
4933 if (is_dotdir_name(entry->d_name))
4934 continue;
4935
4936 struct dirinfo info = dir_get_info(dir, entry);
4937 size_t len = strlen(curpath);
4938 path_append(&curpath[len-1], PA_SEP_HARD, entry->d_name,
4939 sizeof (curpath) - len);
4940
4941 processed_dir_count++;
4942 if (info.attribute & ATTR_DIRECTORY)
4943 {
4944#ifndef SIMULATOR
4945 /* don't follow symlinks to dirs, but try to add it as a search root
4946 * this makes able to avoid looping in recursive symlinks */
4947 if (info.attribute & ATTR_LINK)
4948 add_search_root(curpath);
4949 else
4950#endif /* SIMULATOR */
4951 check_dir(curpath, add_files);
4952 }
4953 else if (add_files)
4954 {
4955 tc_stat.curentry = curpath;
4956
4957 /* Add a new entry to the temporary db file. */
4958 add_tagcache(curpath, info.mtime);
4959
4960 /* Wait until current path for debug screen is read and unset. */
4961 while (tc_stat.syncscreen && tc_stat.curentry != NULL)
4962 yield();
4963
4964 tc_stat.curentry = NULL;
4965 }
4966
4967 str_setlen(curpath, len);
4968 }
4969
4970 closedir(dir);
4971
4972 return success;
4973}
4974
4975void tagcache_screensync_event(void)
4976{
4977 tc_stat.curentry = NULL;
4978}
4979
4980void tagcache_screensync_enable(bool state)
4981{
4982 tc_stat.syncscreen = state;
4983}
4984
4985#ifndef __PCTOOL__
4986/* this is called by the database tool to not pull in global_settings */
4987static
4988#endif
4989void do_tagcache_build(const char *path[])
4990{
4991 struct tagcache_header header;
4992 bool ret;
4993
4994 str_setlen(curpath, 0);
4995 data_size = 0;
4996 total_entry_count = 0;
4997 processed_dir_count = 0;
4998
4999#ifdef HAVE_DIRCACHE
5000 dircache_wait();
5001#endif
5002
5003 logf("updating tagcache");
5004
5005 cachefd = open_db_fd(TAGCACHE_FILE_TEMP, O_RDONLY);
5006 if (cachefd >= 0)
5007 {
5008 logf("skipping, cache already waiting for commit");
5009 close(cachefd);
5010 return ;
5011 }
5012
5013 cachefd = open_db_fd(TAGCACHE_FILE_TEMP, O_RDWR | O_CREAT | O_TRUNC);
5014 if (cachefd < 0)
5015 {
5016 logf("master file open failed: %s", TAGCACHE_FILE_TEMP);
5017 return ;
5018 }
5019
5020 filenametag_fd = open_tag_fd(&header, tag_filename, false);
5021
5022 cpu_boost(true);
5023
5024 logf("Scanning files...");
5025 /* Scan for new files. */
5026 memset(&header, 0, sizeof(struct tagcache_header));
5027 write(cachefd, &header, sizeof(struct tagcache_header));
5028
5029 ret = true;
5030
5031 roots_ll[0].path = path[0];
5032 roots_ll[0].next = NULL;
5033
5034#if defined(HAVE_MULTIVOLUME) && !defined(SIMULATOR) && !defined(__PCTOOL__) && !defined(APPLICATION)
5035 extern bool ns_volume_is_visible(int volume); /*rb_namespace.c*/
5036 /* i is for the path vector, j for the roots_ll array */
5037 int i = 1, j = 1;
5038 bool added = false;
5039 char volnamebuf[NUM_VOLUMES][VOL_MAX_LEN + 1];
5040 /* we can just parse the root directory ('/') and get to any mounted
5041 * volume but we can also enumerate a volume in the root directory
5042 * when this occurs it leads to multiple entries since the files can
5043 * be reached through multiple paths ex, /Foo could also be /SD1/Foo
5044 * we used to hide the volume that was mapped but then when you switch
5045 * from the sd to the internal the paths don't map to the right volume
5046 * instead we will attempt to rewrite the root with any non-hidden volumes
5047 * failing that just leave the paths alone */
5048 if (!strcmp(PATH_ROOTSTR, path[0]))
5049 {
5050 i = 0;
5051 j = 0;
5052 }
5053 /* path can be skipped , but root_ll entries can't */
5054 for(; path[i] && j < MAX_STATIC_ROOTS; i++)
5055 {
5056 /* check if the link target is inside of an existing search root
5057 * don't add if target is inside, we'll scan it later */
5058 if (!added && !strcmp(PATH_ROOTSTR, path[i]))
5059 {
5060 for (int v = 0; v < NUM_VOLUMES; v++)
5061 {
5062 if (ns_volume_is_visible(v))
5063 {
5064 make_volume_root(v, volnamebuf[v]);
5065 roots_ll[j].path = volnamebuf[v];
5066 if (j > 0)
5067 roots_ll[j-1].next = &roots_ll[j];
5068 j++;
5069 added = true;
5070 }
5071 }
5072 if(!added)
5073 j = 1;
5074 added = true;
5075 continue;
5076 }
5077#else
5078 /* i is for the path vector, j for the roots_ll array
5079 * path can be skipped , but root_ll entries can't */
5080 for(int i = 1, j = 1; path[i] && j < MAX_STATIC_ROOTS; i++)
5081 {
5082#endif /*def HAVE_MULTIVOLUME*/
5083 if (search_root_exists(path[i])) /* skip this path */
5084 continue;
5085
5086 roots_ll[j].path = path[i];
5087 roots_ll[j-1].next = &roots_ll[j];
5088 j++;
5089 }
5090
5091 struct search_roots_ll * this;
5092 /* check_dir might add new roots */
5093 for(this = &roots_ll[0]; this; this = this->next)
5094 {
5095 logf("Search root %s", this->path);
5096 strmemccpy(curpath, this->path, sizeof(curpath));
5097
5098 if (ret)
5099 {
5100 if (dir_exists(this->path))
5101 ret = check_dir(this->path, true);
5102 else
5103 logf("Dir not found %s", this->path);
5104 }
5105 }
5106 free_search_roots(&roots_ll[0]);
5107
5108 /* Write the header. */
5109 header.magic = TAGCACHE_MAGIC;
5110 header.datasize = data_size;
5111 header.entry_count = total_entry_count;
5112 lseek(cachefd, 0, SEEK_SET);
5113 write(cachefd, &header, sizeof(struct tagcache_header));
5114 close(cachefd);
5115
5116 if (filenametag_fd >= 0)
5117 {
5118 close(filenametag_fd);
5119 filenametag_fd = -1;
5120 }
5121
5122 if (!ret)
5123 {
5124 logf("Aborted.");
5125 cpu_boost(false);
5126 return ;
5127 }
5128
5129 /* Commit changes to the database. */
5130#ifdef __PCTOOL__
5131 allocate_tempbuf();
5132#endif
5133 if (commit())
5134 {
5135 logf("tagcache built!");
5136 }
5137#ifdef __PCTOOL__
5138 free_tempbuf();
5139#endif
5140
5141#ifdef HAVE_TC_RAMCACHE
5142 if (tcramcache.hdr)
5143 {
5144 /* Import runtime statistics if we just initialized the db. */
5145 if (current_tcmh.serial == 0)
5146 queue_post(&tagcache_queue, Q_IMPORT_CHANGELOG, 0);
5147 }
5148#endif
5149
5150 cpu_boost(false);
5151}
5152
5153#ifndef __PCTOOL__
5154void tagcache_build(void)
5155{
5156 char *vect[MAX_STATIC_ROOTS + 1]; /* +1 to ensure NULL sentinel */
5157 char str[sizeof(global_settings.tagcache_scan_paths)];
5158 strmemccpy(str, global_settings.tagcache_scan_paths, sizeof(str));
5159
5160 int res = split_string(str, ':', vect, MAX_STATIC_ROOTS);
5161 vect[res] = NULL;
5162
5163 do_tagcache_build((const char**)vect);
5164}
5165#endif /* __PCTOOL__ */
5166
5167#ifdef HAVE_TC_RAMCACHE
5168static void load_ramcache(void)
5169{
5170 if (!tcramcache.hdr)
5171 return ;
5172
5173 cpu_boost(true);
5174
5175 /* At first we should load the cache (if exists). */
5176 tc_stat.ramcache = load_tagcache();
5177
5178 if (!tc_stat.ramcache)
5179 {
5180 /* If loading failed, it must indicate some problem with the db
5181 * so disable it entirely to prevent further issues. */
5182 tc_stat.ready = false;
5183 tcramcache.hdr = NULL;
5184 int handle = tcramcache.handle;
5185 tcramcache.handle = 0;
5186 core_free(handle);
5187 }
5188
5189 cpu_boost(false);
5190}
5191
5192void tagcache_unload_ramcache(void)
5193{
5194 tc_stat.ramcache = false;
5195 /* Just to make sure there is no statefile present. */
5196 // remove_db_file(TAGCACHE_STATEFILE);
5197}
5198#endif /* HAVE_TC_RAMCACHE */
5199
5200#ifndef __PCTOOL__
5201
5202/*
5203 * db_file_exists is noinline to minimize stack usage
5204 */
5205static bool NO_INLINE db_file_exists(const char* filename)
5206{
5207 char buf[MAX_PATH];
5208
5209 snprintf(buf, sizeof(buf), "%s/%s", tc_stat.db_path, filename);
5210
5211 return file_exists(buf);
5212}
5213
5214static void tagcache_thread(void)
5215{
5216 struct queue_event ev;
5217 bool check_done = false;
5218 cpu_boost(true);
5219 /* If the previous cache build/update was interrupted, commit
5220 * the changes first in foreground. */
5221 if (db_file_exists(TAGCACHE_FILE_TEMP))
5222 {
5223#if !(defined(__APPLE__) && (CONFIG_PLATFORM & PLATFORM_SDL))
5224 static const char *lines[] = {ID2P(LANG_TAGCACHE_BUSY),
5225 ID2P(LANG_TAGCACHE_UPDATE)};
5226 static const struct text_message message = {lines, 2};
5227
5228 if (gui_syncyesno_run_w_tmo(HZ * 5, YESNO_YES, &message, NULL, NULL) == YESNO_YES)
5229#endif
5230 {
5231 allocate_tempbuf();
5232 commit();
5233 free_tempbuf();
5234 }
5235 }
5236
5237#ifdef HAVE_TC_RAMCACHE
5238#ifdef HAVE_EEPROM_SETTINGS
5239 if (firmware_settings.initialized && firmware_settings.disk_clean
5240 && global_settings.tagcache_ram)
5241 {
5242 check_done = tagcache_dumpload();
5243 }
5244
5245 remove_db_file(TAGCACHE_STATEFILE);
5246#endif /* HAVE_EEPROM_SETTINGS */
5247
5248 /* Allocate space for the tagcache if found on disk. */
5249 if (global_settings.tagcache_ram && !tc_stat.ramcache)
5250 allocate_tagcache();
5251#endif /* HAVE_TC_RAMCACHE */
5252
5253 cpu_boost(false);
5254 tc_stat.initialized = true;
5255
5256 /* Don't delay bootup with the header check but do it on background. */
5257 if (!tc_stat.ready)
5258 {
5259 sleep(HZ);
5260 tagcache_commit_finalize();
5261 }
5262
5263 while (1)
5264 {
5265 run_command_queue(false);
5266
5267 queue_wait_w_tmo(&tagcache_queue, &ev, HZ);
5268
5269 switch (ev.id)
5270 {
5271 case Q_IMPORT_CHANGELOG:
5272 tagcache_import_changelog();
5273 break;
5274
5275 case Q_REBUILD:
5276 remove_files();
5277 remove_db_file(TAGCACHE_FILE_TEMP);
5278 tagcache_build();
5279 break;
5280
5281 case Q_UPDATE:
5282 tagcache_build();
5283#ifdef HAVE_TC_RAMCACHE
5284 load_ramcache();
5285#endif
5286 check_deleted_files();
5287 break ;
5288
5289 case Q_START_SCAN:
5290 check_done = false;
5291 /* fallthrough */
5292 case SYS_TIMEOUT:
5293 if (check_done || !tc_stat.ready)
5294 break ;
5295
5296#ifdef HAVE_TC_RAMCACHE
5297 if (!tc_stat.ramcache && global_settings.tagcache_ram)
5298 {
5299 load_ramcache();
5300 if (global_settings.tagcache_ram == TAGCACHE_RAM_ON)
5301 check_file_refs(global_settings.tagcache_autoupdate);
5302 if (tc_stat.ramcache && global_settings.tagcache_autoupdate)
5303 tagcache_build();
5304 }
5305 else
5306#endif /* HAVE_RC_RAMCACHE */
5307 if (global_settings.tagcache_autoupdate)
5308 {
5309 tagcache_build();
5310
5311 /* This will be very slow unless dircache is enabled
5312 or target is flash based, but do it anyway for
5313 consistency. */
5314 check_deleted_files();
5315 }
5316
5317 logf("tagcache check done");
5318
5319 check_done = true;
5320 break ;
5321
5322 case Q_STOP_SCAN:
5323 break ;
5324
5325 case SYS_POWEROFF:
5326 case SYS_REBOOT:
5327 break ;
5328
5329 case SYS_USB_CONNECTED:
5330 logf("USB: TagCache");
5331 usb_acknowledge(SYS_USB_CONNECTED_ACK);
5332 usb_wait_for_disconnect(&tagcache_queue);
5333 break ;
5334 }
5335 }
5336}
5337
5338bool tagcache_prepare_shutdown(void)
5339{
5340 if (tagcache_get_commit_step() > 0)
5341 return false;
5342
5343 tagcache_stop_scan();
5344 while (read_lock || write_lock)
5345 sleep(1);
5346
5347 return true;
5348}
5349
5350void tagcache_shutdown(void)
5351{
5352 /* Flush the command queue. */
5353 run_command_queue(true);
5354
5355#if defined(HAVE_EEPROM_SETTINGS) && defined(HAVE_TC_RAMCACHE)
5356 if (tc_stat.ramcache)
5357 tagcache_dumpsave();
5358#endif
5359}
5360
5361void tagcache_remove_statefile(void)
5362{
5363 remove_db_file(TAGCACHE_STATEFILE);
5364}
5365
5366static int get_progress(void)
5367{
5368 int total_count = -1;
5369
5370#ifdef HAVE_DIRCACHE
5371 struct dircache_info dcinfo;
5372 dircache_get_info(&dcinfo);
5373 if (dcinfo.status != DIRCACHE_IDLE &&
5374 ((!tc_stat.ramcache) || current_tcmh.tch.entry_count == 0))
5375 {
5376 total_count = dcinfo.entry_count;
5377 }
5378 else
5379#endif /* HAVE_DIRCACHE */
5380#ifdef HAVE_TC_RAMCACHE
5381 {
5382 if (tcramcache.hdr && tc_stat.ramcache)
5383 total_count = current_tcmh.tch.entry_count;
5384 }
5385#endif /* HAVE_TC_RAMCACHE */
5386
5387 if (total_count < 0)
5388 return -1;
5389 else if (total_count == 0)
5390 return 0;
5391 else if (processed_dir_count > total_count)
5392 return 100;
5393 else
5394 return processed_dir_count * 100 / total_count;
5395}
5396
5397struct tagcache_stat* tagcache_get_stat(void)
5398{
5399 tc_stat.total_entries = current_tcmh.tch.entry_count;
5400 tc_stat.progress = get_progress();
5401 tc_stat.processed_entries = processed_dir_count;
5402
5403 return &tc_stat;
5404}
5405
5406void tagcache_start_scan(void)
5407{
5408 queue_post(&tagcache_queue, Q_START_SCAN, 0);
5409}
5410
5411bool tagcache_update(void)
5412{
5413 if (!tc_stat.ready)
5414 return false;
5415
5416 queue_post(&tagcache_queue, Q_UPDATE, 0);
5417 return false;
5418}
5419
5420bool tagcache_rebuild(void)
5421{
5422 queue_post(&tagcache_queue, Q_REBUILD, 0);
5423 return false;
5424}
5425
5426void tagcache_stop_scan(void)
5427{
5428 queue_post(&tagcache_queue, Q_STOP_SCAN, 0);
5429}
5430
5431#endif /* !__PCTOOL__ */
5432
5433
5434void tagcache_init(void)
5435{
5436 memset(&tc_stat, 0, sizeof(struct tagcache_stat));
5437 memset(¤t_tcmh, 0, sizeof(struct master_header));
5438 filenametag_fd = -1;
5439 write_lock = read_lock = 0;
5440
5441#ifndef __PCTOOL__
5442 strmemccpy(tc_stat.db_path, global_settings.tagcache_db_path,
5443 sizeof(tc_stat.db_path));
5444 mutex_init(&command_queue_mutex);
5445 queue_init(&tagcache_queue, true);
5446 create_thread(tagcache_thread, tagcache_stack,
5447 sizeof(tagcache_stack), 0, tagcache_thread_name
5448 IF_PRIO(, PRIORITY_BACKGROUND)
5449 IF_COP(, CPU));
5450#else
5451 /* use default DB path */
5452 strcpy(tc_stat.db_path, ROCKBOX_DIR);
5453 tc_stat.initialized = true;
5454 allocate_tempbuf();
5455 commit();
5456 free_tempbuf();
5457 tc_stat.ready = check_all_headers();
5458#endif
5459}
5460
5461#ifdef __PCTOOL__
5462void tagcache_reverse_scan(void)
5463{
5464 logf("Checking for deleted files");
5465 check_deleted_files();
5466}
5467#endif
5468
5469bool tagcache_is_initialized(void)
5470{
5471 return tc_stat.initialized;
5472}
5473bool tagcache_is_fully_initialized(void)
5474{
5475 return tc_stat.readyvalid;
5476}
5477bool tagcache_is_usable(void)
5478{
5479 return tc_stat.initialized && tc_stat.ready;
5480}
5481#ifdef HAVE_TC_RAMCACHE
5482bool tagcache_is_in_ram(void)
5483{
5484 return tc_stat.ramcache;
5485}
5486#endif
5487int tagcache_get_commit_step(void)
5488{
5489 return tc_stat.commit_step;
5490}
5491int tagcache_get_max_commit_step(void)
5492{
5493 return (int)(SORTED_TAGS_COUNT)+1;
5494}
5495#endif /*!defined(PLUGIN)*/