ini.cpp

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

Generated on Wed Jan 20 23:38:36 2010 for OpenTTD by  doxygen 1.5.6