Index: apps/tagtree.h =================================================================== --- apps/tagtree.h (revision 19219) +++ apps/tagtree.h (working copy) @@ -28,7 +28,7 @@ #define TAGNAVI_VERSION "#! rockbox/tagbrowser/2.0" #define TAGMENU_MAX_ITEMS 64 #define TAGMENU_MAX_MENUS 32 -#define TAGMENU_MAX_FMTS 32 +#define TAGMENU_MAX_FMTS 96 enum table { root = 1, navibrowse, allsubentries, playtrack }; @@ -36,6 +36,7 @@ char *name; int newtable; int extraseek; + int strip; }; bool tagtree_export(void); Index: apps/tagcache.c =================================================================== --- apps/tagcache.c (revision 19219) +++ apps/tagcache.c (working copy) @@ -111,27 +111,31 @@ static long tempbuf_pos; /* Tags we want to get sorted (loaded to the tempbuf). */ -static const int sorted_tags[] = { tag_artist, tag_album, tag_genre, - tag_composer, tag_comment, tag_albumartist, tag_grouping, tag_title }; +static const int sorted_tags[] = { tag_artist, tag_album, tag_genre, + tag_composer, tag_comment, tag_albumartist, tag_grouping, tag_yearalbum, tag_title }; /* Uniqued tags (we can use these tags with filters and conditional clauses). */ -static const int unique_tags[] = { tag_artist, tag_album, tag_genre, - tag_composer, tag_comment, tag_albumartist, tag_grouping }; +static const int unique_tags[] = { tag_artist, tag_album, tag_genre, + tag_composer, tag_comment, tag_albumartist, tag_grouping, tag_yearalbum }; /* Numeric tags (we can use these tags with conditional clauses). */ -static const int numeric_tags[] = { tag_year, tag_discnumber, +static const int numeric_tags[] = { tag_year, tag_discnumber, tag_tracknumber, tag_length, tag_bitrate, tag_playcount, tag_rating, tag_playtime, tag_lastplayed, tag_commitid, tag_mtime, tag_virt_length_min, tag_virt_length_sec, tag_virt_playtime_min, tag_virt_playtime_sec, tag_virt_entryage, tag_virt_autoscore }; -/* String presentation of the tags defined in tagcache.h. Must be in correct order! */ -static const char *tags_str[] = { "artist", "album", "genre", "title", - "filename", "composer", "comment", "albumartist", "grouping", "year", - "discnumber", "tracknumber", "bitrate", "length", "playcount", "rating", +/* String representation of the tags defined in tagcache.h. Must be in correct order! */ +static const char *tags_str[] = { "artist", "album", "genre", "title", + "filename", "composer", "comment", "albumartist", "grouping", "yearalbum", "year", + "discnumber", "tracknumber", "bitrate", "length", "playcount", "rating", "playtime", "lastplayed", "commitid", "mtime" }; +/* String representation of the clauses defined in tagcache.h. Must be in correct order! */ +/* static const char *clause_str[] = { "NONE", "=", "!=", ">", ">=", + "<", "<=", "~", "!~", "^", "!^", "$", "!$", "@" }; */ + /* Status information of the tagcache. */ static struct tagcache_stat tc_stat; @@ -194,7 +198,7 @@ /** Note: This should be (1 + TAG_COUNT) amount of l's. */ -static const char *index_entry_ec = "lllllllllllllllllllll"; +static const char *index_entry_ec = "llllllllllllllllllllll"; static const char *tagcache_header_ec = "lll"; static const char *master_header_ec = "llllll"; @@ -893,13 +897,13 @@ case clause_is_not: return strcasecmp(clause->str, str); case clause_gt: - return 0>strcasecmp(clause->str, str); + return 0 > strcasecmp(clause->str, str); case clause_gteq: - return 0>=strcasecmp(clause->str, str); + return 0 >= strcasecmp(clause->str, str); case clause_lt: - return 0str, str); + return 0 < strcasecmp(clause->str, str); case clause_lteq: - return 0<=strcasecmp(clause->str, str); + return 0 <= strcasecmp(clause->str, str); case clause_contains: return (strcasestr(str, clause->str) != NULL); case clause_not_contains: @@ -914,7 +918,6 @@ return !str_ends_with(str, clause->str); case clause_oneof: return str_oneof(str, clause->str); - default: logf("Incorrect tag: %d", clause->type); } @@ -955,8 +958,9 @@ str = tfe->tag_data; } } - - if (!check_against_clause(seek, str, clause[i])) + + /* Handle case where str is empty - pass in "" to allow filters to work */ + if (!check_against_clause(seek, ((str[0] == '\0') ? UNTAGGED : str), clause[i])) return false; } } @@ -981,21 +985,18 @@ if (tfe.tag_length >= (int)sizeof(str)) { logf("Too long tag read!"); - break ; + break; } read(fd, str, tfe.tag_length); - - /* Check if entry has been deleted. */ - if (str[0] == '\0') - break; } - - if (!check_against_clause(seek, str, clause[i])) + + /* Handle case where str is empty - pass in "" to allow filters to work */ + if (!check_against_clause(seek, ((str[0] == '\0') ? UNTAGGED : str), clause[i])) return false; } } - + return true; } @@ -1277,36 +1278,30 @@ } bool tagcache_search_add_clause(struct tagcache_search *tcs, - struct tagcache_search_clause *clause) + struct tagcache_search_clause *clause, + bool addfullclause) { - int i; - - if (tcs->clause_count >= TAGCACHE_MAX_CLAUSES) + if (!tagcache_is_numeric_tag(clause->tag) && tcs->idxfd[clause->tag] < 0) { - logf("Too many clauses"); - return false; + char buf[MAX_PATH]; + + snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, clause->tag); + tcs->idxfd[clause->tag] = open(buf, O_RDONLY); } - /* Check if there is already a similar filter in present (filters are - * much faster than clauses). - */ - for (i = 0; i < tcs->filter_count; i++) + /* Only add the full clause if required */ + if (addfullclause) { - if (tcs->filter_tag[i] == clause->tag) - return true; + if (tcs->clause_count >= TAGCACHE_MAX_CLAUSES) + { + logf("Too many clauses"); + return false; + } + + tcs->clause[tcs->clause_count] = clause; + tcs->clause_count++; } - - if (!tagcache_is_numeric_tag(clause->tag) && tcs->idxfd[clause->tag] < 0) - { - char buf[MAX_PATH]; - snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, clause->tag); - tcs->idxfd[clause->tag] = open(buf, O_RDONLY); - } - - tcs->clause[tcs->clause_count] = clause; - tcs->clause_count++; - return true; } @@ -1663,6 +1658,7 @@ int path_length = strlen(path); bool has_albumartist; bool has_grouping; + char *yearalbum, buf[MAX_PATH]; if (cachefd < 0) return ; @@ -1780,13 +1776,21 @@ entry.tag_offset[tag_length] = id3.length; entry.tag_offset[tag_bitrate] = id3.bitrate; entry.tag_offset[tag_mtime] = mtime; - + /* String tags. */ has_albumartist = id3.albumartist != NULL && strlen(id3.albumartist) > 0; has_grouping = id3.grouping != NULL && strlen(id3.grouping) > 0; + if (id3.album != NULL && id3.year > 0) + { + snprintf(buf, sizeof buf, "%d %s", id3.year, id3.album); + yearalbum = buf; + } + else + yearalbum = id3.album; + ADD_TAG(entry, tag_filename, &path); ADD_TAG(entry, tag_title, &id3.title); ADD_TAG(entry, tag_artist, &id3.artist); @@ -1810,11 +1814,12 @@ { ADD_TAG(entry, tag_grouping, &id3.title); } + ADD_TAG(entry, tag_yearalbum, &yearalbum); entry.data_length = offset; - + /* Write the header */ write(cachefd, &entry, sizeof(struct temp_file_entry)); - + /* And tags also... Correct order is critical */ write_item(path); write_item(id3.title); @@ -1839,7 +1844,8 @@ { write_item(id3.title); } - total_entry_count++; + write_item(yearalbum); + total_entry_count++; } static bool tempbuf_insert(char *str, int id, int idx_id, bool unique) Index: apps/tagcache.h =================================================================== --- apps/tagcache.h (revision 19219) +++ apps/tagcache.h (working copy) @@ -25,11 +25,11 @@ #include "metadata.h" /** - Note: When adding new tags, make sure to update index_entry_ec in + Note: When adding new tags, make sure to update index_entry_ec in tagcache.c and bump up the header version too. */ enum tag_type { tag_artist = 0, tag_album, tag_genre, tag_title, - tag_filename, tag_composer, tag_comment, tag_albumartist, tag_grouping, tag_year, + tag_filename, tag_composer, tag_comment, tag_albumartist, tag_grouping, tag_yearalbum, tag_year, tag_discnumber, tag_tracknumber, tag_bitrate, tag_length, tag_playcount, tag_rating, tag_playtime, tag_lastplayed, tag_commitid, tag_mtime, /* Real tags end here, count them. */ @@ -49,14 +49,14 @@ #define IDX_BUF_DEPTH 64 /* Tag Cache Header version 'TCHxx'. Increment when changing internal structures. */ -#define TAGCACHE_MAGIC 0x5443480c +#define TAGCACHE_MAGIC 0x5443481c /* How much to allocate extra space for ramcache. */ #define TAGCACHE_RESERVE 32768 -/** +/** * Define how long one entry must be at least (longer -> less memory at commit). - * Must be at least 4 bytes in length for correct alignment. + * Must be at least 4 bytes in length for correct alignment. */ #define TAGFILE_ENTRY_CHUNK_LENGTH 8 @@ -74,7 +74,7 @@ /* Idle time before committing events in the command queue. */ #define TAGCACHE_COMMAND_QUEUE_COMMIT_DELAY HZ*2 -#define TAGCACHE_MAX_FILTERS 4 +#define TAGCACHE_MAX_FILTERS 8 #define TAGCACHE_MAX_CLAUSES 32 /* Tag database files. */ @@ -195,9 +195,10 @@ bool tagcache_search_add_filter(struct tagcache_search *tcs, int tag, int seek); bool tagcache_search_add_clause(struct tagcache_search *tcs, - struct tagcache_search_clause *clause); + struct tagcache_search_clause *clause, + bool addfullclause); bool tagcache_get_next(struct tagcache_search *tcs); -bool tagcache_retrieve(struct tagcache_search *tcs, int idxid, +bool tagcache_retrieve(struct tagcache_search *tcs, int idxid, int tag, char *buf, long size); void tagcache_search_finish(struct tagcache_search *tcs); long tagcache_get_numeric(const struct tagcache_search *tcs, int tag); Index: apps/tagtree.c =================================================================== --- apps/tagtree.c (revision 19219) +++ apps/tagtree.c (working copy) @@ -19,10 +19,12 @@ * ****************************************************************************/ -/** +/** * Basic structure on this file was copied from dbtree.c and modified to * support the tag cache interface. */ +/* #define LOGF_ENABLE */ + #include #include #include @@ -38,6 +40,7 @@ #include "tagtree.h" #include "lang.h" #include "logf.h" +#include "string.h" #include "playlist.h" #include "keyboard.h" #include "gui/list.h" @@ -81,11 +84,21 @@ menu_load, }; +/* String presentation of the tags defined in tagcache.h. Must be in correct order! */ +static const char *tags_str[] = { "artist", "album", "genre", "title", + "filename", "composer", "comment", "albumartist", "grouping", "yearalbum", "year", + "discnumber", "tracknumber", "bitrate", "length", "playcount", "rating", + "playtime", "lastplayed", "commitid", "mtime" }; + +/* String presentation of the clauses defined in tagcache.h. Must be in correct order! */ +/* static const char *clause_str[] = { "NONE", "=", "!=", ">", ">=", + "<", "<=", "~", "!~", "^", "!^", "$", "!$", "@" }; */ + /* Capacity 10 000 entries (for example 10k different artists) */ #define UNIQBUF_SIZE (64*1024) static long *uniqbuf; -#define MAX_TAGS 5 +#define MAX_TAGS 10 #define MAX_MENU_ID_SIZE 32 static struct tagcache_search tcs, tcs2; @@ -217,6 +230,7 @@ MATCH(tag, buf, "albumartist", tag_albumartist); MATCH(tag, buf, "ensemble", tag_albumartist); MATCH(tag, buf, "grouping", tag_grouping); + MATCH(tag, buf, "yearalbum", tag_yearalbum); MATCH(tag, buf, "genre", tag_genre); MATCH(tag, buf, "length", tag_length); MATCH(tag, buf, "Lm", tag_virt_length_min); @@ -1038,7 +1050,7 @@ { struct tagentry *dptr = (struct tagentry *)c->dircache; struct display_format *fmt; - int i; + int i, j; int namebufused = 0; int total_count = 0; int special_entry_count = 0; @@ -1046,8 +1058,10 @@ int tag; bool sort = false; int sort_limit; - int strip; - + bool strip = false; + bool alltracks = false; + char all_tracks_fmt[MAX_PATH]; + if (init #ifdef HAVE_TC_RAMCACHE && !tagcache_is_ramcache() @@ -1064,10 +1078,12 @@ #endif , 0); } - + if (c->currtable == allsubentries) { + alltracks = true; /* cache flag defining whether this is the AllTracks menu */ tag = tag_title; + sort = true; /* default the all subentries to always sort */ level--; } else @@ -1075,111 +1091,159 @@ if (!tagcache_search(tcs, tag)) return -1; - + /* Prevent duplicate entries in the search list. */ tagcache_search_set_uniqbuf(tcs, uniqbuf, UNIQBUF_SIZE); - + if (level || csi->clause_count[0] || tagcache_is_numeric_tag(tag)) sort = true; - + + /* Add filters for each parent level */ for (i = 0; i < level; i++) { if (tagcache_is_numeric_tag(csi->tagorder[i])) { static struct tagcache_search_clause cc; - + memset(&cc, 0, sizeof(struct tagcache_search_clause)); cc.tag = csi->tagorder[i]; cc.type = clause_is; cc.numeric = true; cc.numeric_data = csi->result_seek[i]; - tagcache_search_add_clause(tcs, &cc); + tagcache_search_add_clause(tcs, &cc, true); } else - { - tagcache_search_add_filter(tcs, csi->tagorder[i], - csi->result_seek[i]); - } + tagcache_search_add_filter(tcs, csi->tagorder[i], csi->result_seek[i]); } - + + /* Add clauses for each level in the menu */ for (i = 0; i <= level; i++) { - int j; - for (j = 0; j < csi->clause_count[i]; j++) - tagcache_search_add_clause(tcs, csi->clause[i][j]); + { + tagcache_search_add_clause(tcs, csi->clause[i][j], true); + } } - + current_offset = offset; current_entry_count = 0; c->dirfull = false; - - fmt = NULL; - for (i = 0; i < format_count; i++) + + if (alltracks) { - if (formats[i]->group_id == csi->format_id[level]) - fmt = formats[i]; + /* All Tracks format functionality */ + sort = true; + all_tracks_fmt[0] = '\0'; + + /* Construct the All format name by concatenating the menu level tags */ + for (i = 0; i <= level; i++) + { + strcat(all_tracks_fmt, tags_str[csi->tagorder[i]]); + strcat(all_tracks_fmt, "."); + } + strcat(all_tracks_fmt, "All"); + + /* look for any AllTracks format matching the derived format name */ + fmt = NULL; + for (i = 0; i < format_count; i++) + { + if (!strcasecmp(formats[i]->name, all_tracks_fmt)) + { + /* add all possible clauses for all formats */ + fmt = formats[i]; + for (j = 0; j < fmt->clause_count; j++) + { + tagcache_search_add_clause(tcs, fmt->clause[j], false); + } + } + } } + else + { + fmt = NULL; + for (i = 0; i < format_count; i++) + { + if (formats[i]->group_id == csi->format_id[level]) + { + /* Ensure that tags from all clauses for the fmt are loaded */ + fmt = formats[i]; + for (j = 0; j < fmt->clause_count; j++) + { + tagcache_search_add_clause(tcs, fmt->clause[j], false); + } + } + } + } + /* The assigning of the sort_inverse and sort_limit fields here is a bit arbitrary since any + track title can match any one of the associated formats, each of which can have a different + set of values for the sort inverse and sort limit attributes. */ if (fmt) { sort_inverse = fmt->sort_inverse; sort_limit = fmt->limit; - strip = fmt->strip; sort = true; } else { + /* No global format */ sort_inverse = false; sort_limit = 0; - strip = 0; } - - if (tag != tag_title && tag != tag_filename) + + if ((tag != tag_title) && (tag != tag_filename)) { if (offset == 0) { + /* Adds the option */ dptr->newtable = allsubentries; dptr->name = str(LANG_TAGNAVI_ALL_TRACKS); dptr++; current_entry_count++; + special_entry_count++; } if (offset <= 1) { + /* Adds the option */ dptr->newtable = navibrowse; dptr->name = str(LANG_TAGNAVI_RANDOM); dptr->extraseek = -1; dptr++; current_entry_count++; + special_entry_count++; } - special_entry_count+=2; } - + total_count += special_entry_count; - + + /* Now cycle through each entry and apply any formatting */ while (tagcache_get_next(tcs)) { if (total_count++ < offset) continue; - - dptr->newtable = navibrowse; - if (tag == tag_title || tag == tag_filename) + + dptr->strip = 0; /* set the default strip amount for this entry */ + if ((tag == tag_title) || (tag == tag_filename)) { - dptr->newtable = playtrack; + dptr->newtable = playtrack; dptr->extraseek = tcs->idx_id; } else + { + dptr->newtable = navibrowse; dptr->extraseek = tcs->result_seek; - + } + fmt = NULL; /* Check the format */ for (i = 0; i < format_count; i++) { - if (formats[i]->group_id != csi->format_id[level]) + /* Handle the allsubentries case as well - look for format defined in all_tracks_fmt */ + if (((formats[i]->group_id != csi->format_id[level]) && !alltracks) || + (alltracks && strcasecmp(formats[i]->name, all_tracks_fmt))) continue; - - if (tagcache_check_clauses(tcs, formats[i]->clause, - formats[i]->clause_count)) + + if (tagcache_check_clauses(tcs, formats[i]->clause, formats[i]->clause_count)) { fmt = formats[i]; break; @@ -1197,14 +1261,19 @@ logf("format_str() failed"); return 0; } + + /* Set the strip amount for this track if the title was successfully formatted */ + dptr->strip = fmt->strip; + if (fmt->strip > 0) + strip = true; /* cached strip flag */ } - + dptr->name = &c->name_buffer[namebufused]; if (fmt) namebufused += strlen(buf)+1; else namebufused += tcs->result_len; - + if (namebufused >= c->name_buffer_size) { logf("chunk mode #2: %d", current_entry_count); @@ -1212,6 +1281,7 @@ sort = false; break ; } + if (fmt) strcpy(dptr->name, buf); else @@ -1261,33 +1331,41 @@ } total_count++; } - + tagcache_search_finish(tcs); - + if (!sort && (sort_inverse || sort_limit)) { splashf(HZ*4, ID2P(LANG_SHOWDIR_BUFFER_FULL), total_count); logf("Too small dir buffer"); return 0; } - + if (sort_limit) total_count = MIN(total_count, sort_limit); - + if (strip) { + /* Now perform strip based on the format applied to each track name */ dptr = c->dircache; for (i = 0; i < total_count; i++, dptr++) { - int len = strlen(dptr->name); - - if (len < strip) + /* Skip the special entries in the list */ + if (i < special_entry_count) continue; - - dptr->name = &dptr->name[strip]; + + if (dptr->strip > 0) + { + int len = strlen(dptr->name); + + if (len < dptr->strip) + continue; + + dptr->name = &dptr->name[dptr->strip]; + } } } - + return total_count; }