00001
00002
00003
00004
00005
00006
00007
00008
00009
00012 #include "base_media_base.h"
00013 #include "debug.h"
00014 #include "ini_type.h"
00015 #include "string_func.h"
00016
00017 template <class Tbase_set> const char *BaseMedia<Tbase_set>::ini_set;
00018 template <class Tbase_set> const Tbase_set *BaseMedia<Tbase_set>::used_set;
00019 template <class Tbase_set> Tbase_set *BaseMedia<Tbase_set>::available_sets;
00020
00025 #define fetch_metadata(name) \
00026 item = metadata->GetItem(name, false); \
00027 if (item == NULL || StrEmpty(item->value)) { \
00028 DEBUG(grf, 0, "Base " SET_TYPE "set detail loading: %s field missing", name); \
00029 return false; \
00030 }
00031
00032 template <class T, size_t Tnum_files, Subdirectory Tsubdir>
00033 bool BaseSet<T, Tnum_files, Tsubdir>::FillSetDetails(IniFile *ini, const char *path, bool allow_empty_filename)
00034 {
00035 memset(this, 0, sizeof(*this));
00036
00037 IniGroup *metadata = ini->GetGroup("metadata");
00038 IniItem *item;
00039
00040 fetch_metadata("name");
00041 this->name = strdup(item->value);
00042
00043 fetch_metadata("description");
00044 this->description[strdup("")] = strdup(item->value);
00045
00046
00047 for (const IniItem *item = metadata->item; item != NULL; item = item->next) {
00048 if (strncmp("description.", item->name, 12) != 0) continue;
00049
00050 this->description[strdup(item->name + 12)] = strdup(item->value);
00051 }
00052
00053 fetch_metadata("shortname");
00054 for (uint i = 0; item->value[i] != '\0' && i < 4; i++) {
00055 this->shortname |= ((uint8)item->value[i]) << (i * 8);
00056 }
00057
00058 fetch_metadata("version");
00059 this->version = atoi(item->value);
00060
00061 item = metadata->GetItem("fallback", false);
00062 this->fallback = (item != NULL && strcmp(item->value, "0") != 0 && strcmp(item->value, "false") != 0);
00063
00064
00065 IniGroup *files = ini->GetGroup("files");
00066 IniGroup *md5s = ini->GetGroup("md5s");
00067 IniGroup *origin = ini->GetGroup("origin");
00068 for (uint i = 0; i < Tnum_files; i++) {
00069 MD5File *file = &this->files[i];
00070
00071 item = files->GetItem(BaseSet<T, Tnum_files, Tsubdir>::file_names[i], false);
00072 if (item == NULL || (item->value == NULL && !allow_empty_filename)) {
00073 DEBUG(grf, 0, "No " SET_TYPE " file for: %s", BaseSet<T, Tnum_files, Tsubdir>::file_names[i]);
00074 return false;
00075 }
00076
00077 const char *filename = item->value;
00078 if (filename == NULL) {
00079 file->filename = NULL;
00080
00081 this->valid_files++;
00082 this->found_files++;
00083 continue;
00084 }
00085
00086 file->filename = str_fmt("%s%s", path, filename);
00087
00088
00089 item = md5s->GetItem(filename, false);
00090 if (item == NULL) {
00091 DEBUG(grf, 0, "No MD5 checksum specified for: %s", filename);
00092 return false;
00093 }
00094 char *c = item->value;
00095 for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
00096 uint j;
00097 if ('0' <= *c && *c <= '9') {
00098 j = *c - '0';
00099 } else if ('a' <= *c && *c <= 'f') {
00100 j = *c - 'a' + 10;
00101 } else if ('A' <= *c && *c <= 'F') {
00102 j = *c - 'A' + 10;
00103 } else {
00104 DEBUG(grf, 0, "Malformed MD5 checksum specified for: %s", filename);
00105 return false;
00106 }
00107 if (i % 2 == 0) {
00108 file->hash[i / 2] = j << 4;
00109 } else {
00110 file->hash[i / 2] |= j;
00111 }
00112 }
00113
00114
00115 item = filename == NULL ? NULL : origin->GetItem(filename, false);
00116 if (item == NULL) item = origin->GetItem("default", false);
00117 if (item == NULL) {
00118 DEBUG(grf, 1, "No origin warning message specified for: %s", filename);
00119 file->missing_warning = strdup("");
00120 } else {
00121 file->missing_warning = strdup(item->value);
00122 }
00123
00124 switch (file->CheckMD5(Tsubdir)) {
00125 case MD5File::CR_MATCH:
00126 this->valid_files++;
00127
00128 case MD5File::CR_MISMATCH:
00129 this->found_files++;
00130 break;
00131
00132 case MD5File::CR_NO_FILE:
00133 break;
00134 }
00135 }
00136
00137 return true;
00138 }
00139
00140 template <class Tbase_set>
00141 bool BaseMedia<Tbase_set>::AddFile(const char *filename, size_t basepath_length)
00142 {
00143 bool ret = false;
00144 DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename);
00145
00146 Tbase_set *set = new Tbase_set();
00147 IniFile *ini = new IniFile();
00148 ini->LoadFromDisk(filename);
00149
00150 char *path = strdup(filename + basepath_length);
00151 char *psep = strrchr(path, PATHSEPCHAR);
00152 if (psep != NULL) {
00153 psep[1] = '\0';
00154 } else {
00155 *path = '\0';
00156 }
00157
00158 if (set->FillSetDetails(ini, path)) {
00159 Tbase_set *duplicate = NULL;
00160 for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != NULL; c = c->next) {
00161 if (strcmp(c->name, set->name) == 0 || c->shortname == set->shortname) {
00162 duplicate = c;
00163 break;
00164 }
00165 }
00166 if (duplicate != NULL) {
00167
00168 if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
00169 duplicate->valid_files > set->valid_files) {
00170 DEBUG(grf, 1, "Not adding %s (%i) as base " SET_TYPE " set (duplicate)", set->name, set->version);
00171 delete set;
00172 } else {
00173 Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
00174 while (*prev != duplicate) prev = &(*prev)->next;
00175
00176 *prev = set;
00177 set->next = duplicate->next;
00178
00179 duplicate->next = NULL;
00180
00181
00182
00183
00184 if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;
00185
00186 DEBUG(grf, 1, "Removing %s (%i) as base " SET_TYPE " set (duplicate)", duplicate->name, duplicate->version);
00187 delete duplicate;
00188 ret = true;
00189 }
00190 } else {
00191 Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
00192 while (*last != NULL) last = &(*last)->next;
00193
00194 *last = set;
00195 ret = true;
00196 }
00197 if (ret) {
00198 DEBUG(grf, 1, "Adding %s (%i) as base " SET_TYPE " set", set->name, set->version);
00199 }
00200 } else {
00201 delete set;
00202 }
00203 free(path);
00204
00205 delete ini;
00206 return ret;
00207 }
00208
00209 template <class Tbase_set>
00210 bool BaseMedia<Tbase_set>::SetSet(const char *name)
00211 {
00212 extern void CheckExternalFiles();
00213
00214 if (StrEmpty(name)) {
00215 if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
00216 CheckExternalFiles();
00217 return true;
00218 }
00219
00220 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00221 if (strcmp(name, s->name) == 0) {
00222 BaseMedia<Tbase_set>::used_set = s;
00223 CheckExternalFiles();
00224 return true;
00225 }
00226 }
00227 return false;
00228 }
00229
00230 template <class Tbase_set>
00231 char *BaseMedia<Tbase_set>::GetSetsList(char *p, const char *last)
00232 {
00233 p += seprintf(p, last, "List of " SET_TYPE " sets:\n");
00234 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00235 p += seprintf(p, last, "%18s: %s", s->name, s->GetDescription());
00236 int invalid = s->GetNumInvalid();
00237 if (invalid != 0) {
00238 int missing = s->GetNumMissing();
00239 if (missing == 0) {
00240 p += seprintf(p, last, " (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
00241 } else {
00242 p += seprintf(p, last, " (unuseable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
00243 }
00244 } else {
00245 p += seprintf(p, last, "\n");
00246 }
00247 }
00248 p += seprintf(p, last, "\n");
00249
00250 return p;
00251 }
00252
00253 #if defined(ENABLE_NETWORK)
00254 #include "network/network_content.h"
00255
00256 template <class Tbase_set>
00257 bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
00258 {
00259 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00260 if (s->GetNumMissing() != 0) continue;
00261
00262 if (s->shortname != ci->unique_id) continue;
00263 if (!md5sum) return true;
00264
00265 byte md5[16];
00266 memset(md5, 0, sizeof(md5));
00267 for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
00268 for (uint j = 0; j < sizeof(md5); j++) {
00269 md5[j] ^= s->files[i].hash[j];
00270 }
00271 }
00272 if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return true;
00273 }
00274
00275 return false;
00276 }
00277
00278 #else
00279
00280 template <class Tbase_set>
00281 bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
00282 {
00283 return false;
00284 }
00285
00286 #endif
00287
00288 template <class Tbase_set>
00289 int BaseMedia<Tbase_set>::GetNumSets()
00290 {
00291 int n = 0;
00292 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00293 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
00294 n++;
00295 }
00296 return n;
00297 }
00298
00299 template <class Tbase_set>
00300 int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
00301 {
00302 int n = 0;
00303 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00304 if (s == BaseMedia<Tbase_set>::used_set) return n;
00305 if (s->GetNumMissing() != 0) continue;
00306 n++;
00307 }
00308 return -1;
00309 }
00310
00311 template <class Tbase_set>
00312 const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
00313 {
00314 for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != NULL; s = s->next) {
00315 if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
00316 if (index == 0) return s;
00317 index--;
00318 }
00319 error("Base" SET_TYPE "::GetSet(): index %d out of range", index);
00320 }
00321
00322 template <class Tbase_set>
00323 const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
00324 {
00325 return BaseMedia<Tbase_set>::used_set;
00326 }
00327
00333 #define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
00334 template const char *repl_type::ini_set; \
00335 template const char *repl_type::GetExtension(); \
00336 template bool repl_type::AddFile(const char *filename, size_t pathlength); \
00337 template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
00338 template bool repl_type::SetSet(const char *name); \
00339 template char *repl_type::GetSetsList(char *p, const char *last); \
00340 template int repl_type::GetNumSets(); \
00341 template int repl_type::GetIndexOfUsedSet(); \
00342 template const set_type *repl_type::GetSet(int index); \
00343 template const set_type *repl_type::GetUsedSet(); \
00344 template bool repl_type::DetermineBestSet();
00345