ini.cpp

Go to the documentation of this file.
00001 /* $Id: ini.cpp 15707 2009-03-14 00:26:03Z michi_cc $ */
00002 
00005 #include "stdafx.h"
00006 #include "core/alloc_func.hpp"
00007 #include "core/math_func.hpp"
00008 #include "core/mem_func.hpp"
00009 #include "debug.h"
00010 #include "ini_type.h"
00011 #include "string_func.h"
00012 #include "fileio_func.h"
00013 
00014 #if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500)
00015 # define WITH_FDATASYNC
00016 # include <unistd.h>
00017 #endif
00018 
00019 #ifdef WIN32
00020 # include <shellapi.h>
00021 #endif
00022 
00023 IniItem::IniItem(IniGroup *parent, const char *name, size_t len) : next(NULL), value(NULL), comment(NULL)
00024 {
00025   if (len == 0) len = strlen(name);
00026 
00027   this->name = strndup(name, len);
00028   *parent->last_item = this;
00029   parent->last_item = &this->next;
00030 }
00031 
00032 IniItem::~IniItem()
00033 {
00034   free(this->name);
00035   free(this->value);
00036   free(this->comment);
00037 
00038   delete this->next;
00039 }
00040 
00041 void IniItem::SetValue(const char *value)
00042 {
00043   free(this->value);
00044   this->value = strdup(value);
00045 }
00046 
00047 IniGroup::IniGroup(IniFile *parent, const char *name, size_t len) : next(NULL), type(IGT_VARIABLES), item(NULL), comment(NULL)
00048 {
00049   if (len == 0) len = strlen(name);
00050 
00051   this->name = strndup(name, len);
00052   this->last_item = &this->item;
00053   *parent->last_group = this;
00054   parent->last_group = &this->next;
00055 
00056   if (parent->list_group_names == NULL) return;
00057 
00058   for (uint i = 0; parent->list_group_names[i] != NULL; i++) {
00059     if (strcmp(this->name, parent->list_group_names[i]) == 0) {
00060       this->type = IGT_LIST;
00061       return;
00062     }
00063   }
00064 }
00065 
00066 IniGroup::~IniGroup()
00067 {
00068   free(this->name);
00069   free(this->comment);
00070 
00071   delete this->item;
00072   delete this->next;
00073 }
00074 
00075 IniItem *IniGroup::GetItem(const char *name, bool create)
00076 {
00077   for (IniItem *item = this->item; item != NULL; item = item->next) {
00078     if (strcmp(item->name, name) == 0) return item;
00079   }
00080 
00081   if (!create) return NULL;
00082 
00083   /* otherwise make a new one */
00084   return new IniItem(this, name, strlen(name));
00085 }
00086 
00087 void IniGroup::Clear()
00088 {
00089   delete this->item;
00090   this->item = NULL;
00091   this->last_item = &this->item;
00092 }
00093 
00094 IniFile::IniFile(const char **list_group_names) : group(NULL), comment(NULL), list_group_names(list_group_names)
00095 {
00096   this->last_group = &this->group;
00097 }
00098 
00099 IniFile::~IniFile()
00100 {
00101   free(this->comment);
00102   delete this->group;
00103 }
00104 
00105 IniGroup *IniFile::GetGroup(const char *name, size_t len)
00106 {
00107   if (len == 0) len = strlen(name);
00108 
00109   /* does it exist already? */
00110   for (IniGroup *group = this->group; group != NULL; group = group->next) {
00111     if (!memcmp(group->name, name, len) && group->name[len] == 0) {
00112       return group;
00113     }
00114   }
00115 
00116   /* otherwise make a new one */
00117   IniGroup *group = new IniGroup(this, name, len);
00118   group->comment = strdup("\n");
00119   return group;
00120 }
00121 
00122 void IniFile::RemoveGroup(const char *name)
00123 {
00124   size_t len = strlen(name);
00125   IniGroup *prev = NULL;
00126   IniGroup *group;
00127 
00128   /* does it exist already? */
00129   for (group = this->group; group != NULL; prev = group, group = group->next) {
00130     if (memcmp(group->name, name, len) == 0) {
00131       break;
00132     }
00133   }
00134 
00135   if (group == NULL) return;
00136 
00137   if (prev != NULL) {
00138     prev->next = prev->next->next;
00139     if (this->last_group == &group->next) this->last_group = &prev->next;
00140   } else {
00141     this->group = this->group->next;
00142     if (this->last_group == &group->next) this->last_group = &this->group;
00143   }
00144 
00145   group->next = NULL;
00146   delete group;
00147 }
00148 
00149 void IniFile::LoadFromDisk(const char *filename)
00150 {
00151   assert(this->last_group == &this->group);
00152 
00153   char buffer[1024];
00154   IniGroup *group = NULL;
00155 
00156   char *comment = NULL;
00157   uint comment_size = 0;
00158   uint comment_alloc = 0;
00159 
00160   size_t end;
00161   /*
00162    * Now we are going to open a file that contains no more than simple
00163    * plain text. That would raise the question: "why open the file as
00164    * if it is a binary file?". That's simple... Microsoft, in all
00165    * their greatness and wisdom decided it would be useful if ftell
00166    * is aware of '\r\n' and "sees" that as a single character. The
00167    * easiest way to test for that situation is by searching for '\n'
00168    * and decrease the value every time you encounter a '\n'. This will
00169    * thus also make ftell "see" the '\r' when it is not there, so the
00170    * result of ftell will be highly unreliable. So to work around this
00171    * marvel of wisdom we have to open in as a binary file.
00172    */
00173   FILE *in = FioFOpenFile(filename, "rb", DATA_DIR, &end);
00174   if (in == NULL) return;
00175 
00176   end += ftell(in);
00177 
00178   /* for each line in the file */
00179   while ((size_t)ftell(in) < end && fgets(buffer, sizeof(buffer), in)) {
00180     char c, *s;
00181     /* trim whitespace from the left side */
00182     for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
00183 
00184     /* trim whitespace from right side. */
00185     char *e = s + strlen(s);
00186     while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
00187     *e = '\0';
00188 
00189     /* skip comments and empty lines */
00190     if (*s == '#' || *s == ';' || *s == '\0') {
00191       uint ns = comment_size + (e - s + 1);
00192       uint a = comment_alloc;
00193       /* add to comment */
00194       if (ns > a) {
00195         a = max(a, 128U);
00196         do a *= 2; while (a < ns);
00197         comment = ReallocT(comment, comment_alloc = a);
00198       }
00199       uint pos = comment_size;
00200       comment_size += (e - s + 1);
00201       comment[pos + e - s] = '\n'; // comment newline
00202       memcpy(comment + pos, s, e - s); // copy comment contents
00203       continue;
00204     }
00205 
00206     /* it's a group? */
00207     if (s[0] == '[') {
00208       if (e[-1] != ']') {
00209         ShowInfoF("ini: invalid group name '%s'", buffer);
00210       } else {
00211         e--;
00212       }
00213       s++; // skip [
00214       group = new IniGroup(this, s, e - s);
00215       if (comment_size) {
00216         group->comment = strndup(comment, comment_size);
00217         comment_size = 0;
00218       }
00219     } else if (group) {
00220       char *t;
00221       /* find end of keyname */
00222       if (*s == '\"') {
00223         s++;
00224         for (t = s; *t != '\0' && *t != '\"'; t++) {}
00225         if (*t == '\"') *t = ' ';
00226       } else {
00227         for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
00228       }
00229 
00230       /* it's an item in an existing group */
00231       IniItem *item = new IniItem(group, s, t - s);
00232       if (comment_size) {
00233         item->comment = strndup(comment, comment_size);
00234         comment_size = 0;
00235       }
00236 
00237       /* find start of parameter */
00238       while (*t == '=' || *t == ' ' || *t == '\t') t++;
00239 
00240 
00241       /* remove starting quotation marks */
00242       if (*t == '\"') t++;
00243       /* remove ending quotation marks */
00244       e = t + strlen(t);
00245       if (e > t && e[-1] == '\"') e--;
00246       *e = '\0';
00247 
00248       item->value = strndup(t, e - t);
00249     } else {
00250       /* it's an orphan item */
00251       ShowInfoF("ini: '%s' outside of group", buffer);
00252     }
00253   }
00254 
00255   if (comment_size > 0) {
00256     this->comment = strndup(comment, comment_size);
00257     comment_size = 0;
00258   }
00259 
00260   free(comment);
00261   fclose(in);
00262 }
00263 
00264 bool IniFile::SaveToDisk(const char *filename)
00265 {
00266   /*
00267    * First write the configuration to a (temporary) file and then rename
00268    * that file. This to prevent that when OpenTTD crashes during the save
00269    * you end up with a truncated configuration file.
00270    */
00271   char file_new[MAX_PATH];
00272 
00273   strecpy(file_new, filename, lastof(file_new));
00274   strecat(file_new, ".new", lastof(file_new));
00275   FILE *f = fopen(file_new, "w");
00276   if (f == NULL) return false;
00277 
00278   for (const IniGroup *group = this->group; group != NULL; group = group->next) {
00279     if (group->comment) fputs(group->comment, f);
00280     fprintf(f, "[%s]\n", group->name);
00281     for (const IniItem *item = group->item; item != NULL; item = item->next) {
00282       assert(item->value != NULL);
00283       if (item->comment != NULL) fputs(item->comment, f);
00284 
00285       /* protect item->name with quotes if needed */
00286       if (strchr(item->name, ' ') != NULL) {
00287         fprintf(f, "\"%s\"", item->name);
00288       } else {
00289         fprintf(f, "%s", item->name);
00290       }
00291 
00292       fprintf(f, " = %s\n", item->value);
00293     }
00294   }
00295   if (this->comment) fputs(this->comment, f);
00296 
00297 /*
00298  * POSIX (and friends) do not guarantee that when a file is closed it is
00299  * flushed to the disk. So we manually flush it do disk if we have the
00300  * APIs to do so. We only need to flush the data as the metadata itself
00301  * (modification date etc.) is not important to us; only the real data is.
00302  */
00303 #ifdef WITH_FDATASYNC
00304   int ret = fdatasync(fileno(f));
00305   fclose(f);
00306   if (ret != 0) return false;
00307 #else
00308   fclose(f);
00309 #endif
00310 
00311 #if defined(WIN32) || defined(WIN64)
00312   /* Allocate space for one more \0 character. */
00313   TCHAR tfilename[MAX_PATH + 1], tfile_new[MAX_PATH + 1];
00314   _tcsncpy(tfilename, OTTD2FS(filename), MAX_PATH);
00315   _tcsncpy(tfile_new, OTTD2FS(file_new), MAX_PATH);
00316   /* SHFileOperation wants a double '\0' terminated string. */
00317   tfilename[MAX_PATH - 1] = '\0';
00318   tfile_new[MAX_PATH - 1] = '\0';
00319   tfilename[_tcslen(tfilename) + 1] = '\0';
00320   tfile_new[_tcslen(tfile_new) + 1] = '\0';
00321 
00322   /* Rename file without any user confirmation. */
00323   SHFILEOPSTRUCT shfopt;
00324   MemSetT(&shfopt, 0);
00325   shfopt.wFunc  = FO_MOVE;
00326   shfopt.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_SILENT;
00327   shfopt.pFrom  = tfile_new;
00328   shfopt.pTo    = tfilename;
00329   SHFileOperation(&shfopt);
00330 #else
00331   rename(file_new, filename);
00332 #endif
00333 
00334   return true;
00335 }

Generated on Tue Jul 21 18:48:23 2009 for OpenTTD by  doxygen 1.5.6