blob: 7948d9f7e6d16ec7bf7bc5912fa13d899c607b76 [file] [log] [blame]
jerrymf1579332013-02-07 01:56:28 +00001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2011. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
5/*----------------------------------------------------------------------------*/
6
7#include "Preferences.h"
8
9#include "NetworkCommunication/UsageReporting.h"
10#include "Synchronized.h"
11#include "WPIErrors.h"
12
13#include <stdio.h>
14#include <algorithm>
15
16/** Private NI function needed to write to the VxWorks target */
17extern "C" int Priv_SetWriteFileAllowed(UINT32 enable);
18
19/** The Preferences table name */
20static const char *kTableName = "Preferences";
21/** The value of the save field */
22static const char *kSaveField = "~S A V E~";
23/** The file to save to */
24static const char *kFileName = "/c/wpilib-preferences.ini";
25/** The characters to put between a field and value */
26static const char *kValuePrefix = "=\"";
27/** The characters to put after the value */
28static const char *kValueSuffix = "\"\n";
29/** The singleton instance */
30Preferences *Preferences::_instance = NULL;
31
32Preferences::Preferences() :
33 m_fileLock(NULL),
34 m_fileOpStarted(NULL),
35 m_tableLock(NULL),
36 m_readTask("PreferencesReadTask", (FUNCPTR)Preferences::InitReadTask),
37 m_writeTask("PreferencesWriteTask", (FUNCPTR)Preferences::InitWriteTask)
38{
39 m_fileLock = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE | SEM_DELETE_SAFE);
40 m_fileOpStarted = semBCreate (SEM_Q_PRIORITY, SEM_EMPTY);
41 m_tableLock = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE | SEM_DELETE_SAFE);
42
43 Synchronized sync(m_fileLock);
44 m_readTask.Start((UINT32)this);
45 semTake(m_fileOpStarted, WAIT_FOREVER);
46
47 NetworkTable::GetTable(kTableName)->PutBoolean(kSaveField, false);
48 NetworkTable::GetTable(kTableName)->AddTableListener(this);
49
50 nUsageReporting::report(nUsageReporting::kResourceType_Preferences, 0);
51}
52
53Preferences::~Preferences()
54{
55 semTake(m_tableLock, WAIT_FOREVER);
56 semDelete(m_tableLock);
57 semTake(m_fileLock, WAIT_FOREVER);
58 semDelete(m_fileOpStarted);
59 semDelete(m_fileLock);
60}
61
62/**
63 * Get the one and only {@link Preferences} object
64 * @return pointer to the {@link Preferences}
65 */
66Preferences *Preferences::GetInstance()
67{
68 if (_instance == NULL)
69 _instance = new Preferences;
70 return _instance;
71}
72
73/**
74 * Returns a vector of all the keys
75 * @return a vector of the keys
76 */
77std::vector<std::string> Preferences::GetKeys()
78{
79 return m_keys;
80}
81
82/**
83 * Returns the string at the given key. If this table does not have a value
84 * for that position, then the given defaultValue will be returned.
85 * @param key the key
86 * @param defaultValue the value to return if none exists in the table
87 * @return either the value in the table, or the defaultValue
88 */
89std::string Preferences::GetString(const char *key, const char *defaultValue)
90{
91 std::string value = Get(key);
92 return value.empty() ? defaultValue : value;
93}
94
95/**
96 * Returns the string at the given key. If this table does not have a value
97 * for that position, then the given defaultValue will be returned.
98 * @param key the key
99 * @param value the buffer to copy the value into
100 * @param valueSize the size of value
101 * @param defaultValue the value to return if none exists in the table
102 * @return The size of the returned string
103 */
104int Preferences::GetString(const char *key, char *value, int valueSize, const char *defaultValue)
105{
106 std::string stringValue = GetString(key, defaultValue);
107 stringValue.copy(value, valueSize);
108 return stringValue.size();
109}
110
111/**
112 * Returns the int at the given key. If this table does not have a value
113 * for that position, then the given defaultValue value will be returned.
114 * @param key the key
115 * @param defaultValue the value to return if none exists in the table
116 * @return either the value in the table, or the defaultValue
117 */
118int Preferences::GetInt(const char *key, int defaultValue)
119{
120 std::string value = Get(key);
121 if (value.empty())
122 return defaultValue;
123
124 return strtol(value.c_str(), NULL, 0);
125}
126
127/**
128 * Returns the double at the given key. If this table does not have a value
129 * for that position, then the given defaultValue value will be returned.
130 * @param key the key
131 * @param defaultValue the value to return if none exists in the table
132 * @return either the value in the table, or the defaultValue
133 */
134double Preferences::GetDouble(const char *key, double defaultValue)
135{
136 std::string value = Get(key);
137 if (value.empty())
138 return defaultValue;
139
140 return strtod(value.c_str(), NULL);
141}
142
143/**
144 * Returns the float at the given key. If this table does not have a value
145 * for that position, then the given defaultValue value will be returned.
146 * @param key the key
147 * @param defaultValue the value to return if none exists in the table
148 * @return either the value in the table, or the defaultValue
149 */
150float Preferences::GetFloat(const char *key, float defaultValue)
151{
152 std::string value = Get(key);
153 if (value.empty())
154 return defaultValue;
155
156 return strtod(value.c_str(), NULL);
157}
158
159/**
160 * Returns the boolean at the given key. If this table does not have a value
161 * for that position, then the given defaultValue value will be returned.
162 * @param key the key
163 * @param defaultValue the value to return if none exists in the table
164 * @return either the value in the table, or the defaultValue
165 */
166bool Preferences::GetBoolean(const char *key, bool defaultValue)
167{
168 std::string value = Get(key);
169 if (value.empty())
170 return defaultValue;
171
172 if (value.compare("true") == 0)
173 return true;
174 else if (value.compare("false") == 0)
175 return false;
176
177 wpi_setWPIErrorWithContext(ParameterOutOfRange, "Boolean value does not contain \"true\" or \"false\"");
178 return false;
179}
180
181/**
182 * Returns the long (INT64) at the given key. If this table does not have a value
183 * for that position, then the given defaultValue value will be returned.
184 * @param key the key
185 * @param defaultValue the value to return if none exists in the table
186 * @return either the value in the table, or the defaultValue
187 */
188INT64 Preferences::GetLong(const char *key, INT64 defaultValue)
189{
190 std::string value = Get(key);
191 if (value.empty())
192 return defaultValue;
193
194 // Ummm... not available in our VxWorks...
195 //return strtoll(value.c_str(), NULL, 0);
196 INT64 intVal;
197 sscanf(value.c_str(), "%lld", &intVal);
198 return intVal;
199}
200
201/**
202 * Puts the given string into the preferences table.
203 *
204 * <p>The value may not have quotation marks, nor may the key
205 * have any whitespace nor an equals sign</p>
206 *
207 * <p>This will <b>NOT</b> save the value to memory between power cycles,
208 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care).
209 * at some point after calling this.</p>
210 * @param key the key
211 * @param value the value
212 */
213void Preferences::PutString(const char *key, const char *value)
214{
215 if (value == NULL)
216 {
217 wpi_setWPIErrorWithContext(NullParameter, "value");
218 return;
219 }
220 if (std::string(value).find_first_of("\"") != std::string::npos)
221 {
222 wpi_setWPIErrorWithContext(ParameterOutOfRange, "value contains illegal characters");
223 return;
224 }
225 Put(key, value);
226}
227
228/**
229 * Puts the given int into the preferences table.
230 *
231 * <p>The key may not have any whitespace nor an equals sign</p>
232 *
233 * <p>This will <b>NOT</b> save the value to memory between power cycles,
234 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
235 * at some point after calling this.</p>
236 * @param key the key
237 * @param value the value
238 */
239void Preferences::PutInt(const char *key, int value)
240{
241 char buf[32];
242 snprintf(buf, 32, "%d", value);
243 Put(key, buf);
244}
245
246/**
247 * Puts the given double into the preferences table.
248 *
249 * <p>The key may not have any whitespace nor an equals sign</p>
250 *
251 * <p>This will <b>NOT</b> save the value to memory between power cycles,
252 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
253 * at some point after calling this.</p>
254 * @param key the key
255 * @param value the value
256 */
257void Preferences::PutDouble(const char *key, double value)
258{
259 char buf[32];
260 snprintf(buf, 32, "%f", value);
261 Put(key, buf);
262}
263
264/**
265 * Puts the given float into the preferences table.
266 *
267 * <p>The key may not have any whitespace nor an equals sign</p>
268 *
269 * <p>This will <b>NOT</b> save the value to memory between power cycles,
270 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
271 * at some point after calling this.</p>
272 * @param key the key
273 * @param value the value
274 */
275void Preferences::PutFloat(const char *key, float value)
276{
277 char buf[32];
278 snprintf(buf, 32, "%f", value);
279 Put(key, buf);
280}
281
282/**
283 * Puts the given boolean into the preferences table.
284 *
285 * <p>The key may not have any whitespace nor an equals sign</p>
286 *
287 * <p>This will <b>NOT</b> save the value to memory between power cycles,
288 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
289 * at some point after calling this.</p>
290 * @param key the key
291 * @param value the value
292 */
293void Preferences::PutBoolean(const char *key, bool value)
294{
295 Put(key, value ? "true" : "false");
296}
297
298/**
299 * Puts the given long (INT64) into the preferences table.
300 *
301 * <p>The key may not have any whitespace nor an equals sign</p>
302 *
303 * <p>This will <b>NOT</b> save the value to memory between power cycles,
304 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
305 * at some point after calling this.</p>
306 * @param key the key
307 * @param value the value
308 */
309void Preferences::PutLong(const char *key, INT64 value)
310{
311 char buf[32];
312 snprintf(buf, 32, "%lld", value);
313 Put(key, buf);
314}
315
316/**
317 * Saves the preferences to a file on the cRIO.
318 *
319 * <p>This should <b>NOT</b> be called often.
320 * Too many writes can damage the cRIO's flash memory.
321 * While it is ok to save once or twice a match, this should never
322 * be called every run of {@link IterativeRobot#TeleopPeriodic()}, etc.</p>
323 *
324 * <p>The actual writing of the file is done in a separate thread.
325 * However, any call to a get or put method will wait until the table is fully saved before continuing.</p>
326 */
327void Preferences::Save()
328{
329 Synchronized sync(m_fileLock);
330 m_writeTask.Start((UINT32)this);
331 semTake(m_fileOpStarted, WAIT_FOREVER);
332}
333
334/**
335 * Returns whether or not there is a key with the given name.
336 * @param key the key
337 * @return if there is a value at the given key
338 */
339bool Preferences::ContainsKey(const char *key)
340{
341 return !Get(key).empty();
342}
343
344/**
345 * Remove a preference
346 * @param key the key
347 */
348void Preferences::Remove(const char *key)
349{
350 m_values.erase(std::string(key));
351 std::vector<std::string>::iterator it = m_keys.begin();
352 for (; it != m_keys.end(); it++)
353 {
354 if (it->compare(key) == 0)
355 {
356 m_keys.erase(it);
357 break;
358 }
359 }
360}
361
362/**
363 * Returns the value at the given key.
364 * @param key the key
365 * @return the value (or empty if none exists)
366 */
367std::string Preferences::Get(const char *key)
368{
369 Synchronized sync(m_tableLock);
370 if (key == NULL)
371 {
372 wpi_setWPIErrorWithContext(NullParameter, "key");
373 return std::string("");
374 }
375 return m_values[std::string(key)];
376}
377
378/**
379 * Puts the given value into the given key position
380 * @param key the key
381 * @param value the value
382 */
383void Preferences::Put(const char *key, std::string value)
384{
385 Synchronized sync(m_tableLock);
386 if (key == NULL)
387 {
388 wpi_setWPIErrorWithContext(NullParameter, "key");
389 return;
390 }
391
392 if (std::string(key).find_first_of("=\n\r \t\"") != std::string::npos)
393 {
394 wpi_setWPIErrorWithContext(ParameterOutOfRange, "key contains illegal characters");
395 return;
396 }
397
398 std::pair<StringMap::iterator, bool> ret =
399 m_values.insert(StringMap::value_type(key, value));
400 if (ret.second)
401 m_keys.push_back(key);
402 else
403 ret.first->second = value;
404
405 NetworkTable::GetTable(kTableName)->PutString(key, value);
406}
407
408/**
409 * The internal method to read from a file.
410 * This will be called in its own thread when the preferences singleton is
411 * first created.
412 */
413void Preferences::ReadTaskRun()
414{
415 Synchronized sync(m_tableLock);
416 semGive(m_fileOpStarted);
417
418 std::string comment;
419
420 FILE *file = NULL;
421 file = fopen(kFileName, "r");
422
423 if (file != NULL)
424 {
425 std::string buffer;
426 while (true)
427 {
428 char value;
429 do
430 {
431 value = fgetc(file);
432 } while (value == ' ' || value == '\t');
433
434 if (value == '\n' || value == ';')
435 {
436 if (value == '\n')
437 {
438 comment += "\n";
439 }
440 else
441 {
442 buffer.clear();
443 for (; value != '\n' && !feof(file); value = fgetc(file))
444 buffer += value;
445 buffer += '\n';
446 comment += buffer;
447 }
448 }
449 else if (value == '[')
450 {
451 // Find the end of the section and the new line after it and throw it away
452 for (; value != ']' && !feof(file); value = fgetc(file));
453 for (; value != '\n' && !feof(file); value = fgetc(file));
454 }
455 else
456 {
457 buffer.clear();
458 for (; value != '=' && !feof(file); )
459 {
460 buffer += value;
461 do
462 {
463 value = fgetc(file);
464 } while (value == ' ' || value == '\t');
465 }
466 std::string name = buffer;
467 buffer.clear();
468
469 bool shouldBreak = false;
470
471 do
472 {
473 value = fgetc(file);
474 } while (value == ' ' || value == '\t');
475
476 if (value == '"')
477 {
478 for (value = fgetc(file); value != '"' && !feof(file); value = fgetc(file))
479 buffer += value;
480
481 // Clear the line
482 while (fgetc(file) != '\n' && !feof(file));
483 }
484 else
485 {
486 for (; value != '\n' && !feof(file);)
487 {
488 buffer += value;
489 do
490 {
491 value = fgetc(file);
492 } while (value == ' ' || value == '\t');
493 }
494 if (feof(file))
495 shouldBreak = true;
496 }
497
498 std::string value = buffer;
499
500 if (!name.empty() && !value.empty())
501 {
502 m_keys.push_back(name);
503 m_values.insert(std::pair<std::string, std::string>(name, value));
504 //NetworkTable::GetTable(kTableName)->PutString(name, value);
505
506 if (!comment.empty())
507 {
508 m_comments.insert(std::pair<std::string, std::string>(name, comment));
509 comment.clear();
510 }
511 }
512
513 if (shouldBreak)
514 break;
515 }
516 }
517 }
518 else
519 {
520 wpi_setWPIErrorWithContext(NoAvailableResources, "Failed to open preferences file.");
521 }
522
523 if (file != NULL)
524 fclose(file);
525
526 if (!comment.empty())
527 m_endComment = comment;
528}
529
530/**
531 * Internal method that actually writes the table to a file.
532 * This is called in its own thread when {@link Preferences#Save() Save()} is called.
533 */
534void Preferences::WriteTaskRun()
535{
536 Synchronized sync(m_tableLock);
537 semGive(m_fileOpStarted);
538
539 FILE *file = NULL;
540 Priv_SetWriteFileAllowed(1);
541 file = fopen(kFileName, "w");
542
543 fputs("[Preferences]\n", file);
544 std::vector<std::string>::iterator it = m_keys.begin();
545 for (; it != m_keys.end(); it++)
546 {
547 std::string key = *it;
548 std::string value = m_values[key];
549 std::string comment = m_comments[key];
550
551 if (!comment.empty())
552 fputs(comment.c_str(), file);
553
554 fputs(key.c_str(), file);
555 fputs(kValuePrefix, file);
556 fputs(value.c_str(), file);
557 fputs(kValueSuffix, file);
558 }
559
560 if (!m_endComment.empty())
561 fputs(m_endComment.c_str(), file);
562
563 if (file != NULL)
564 fclose(file);
565
566 NetworkTable::GetTable(kTableName)->PutBoolean(kSaveField, false);
567}
568
569static bool isKeyAcceptable(const std::string& value) {
570 for (unsigned int i = 0; i < value.length(); i++) {
571 char letter = value.at(i);
572 switch (letter) {
573 case '=':
574 case '\n':
575 case '\r':
576 case ' ':
577 case '\t':
578 return false;
579 }
580 }
581 return true;
582}
583void Preferences::ValueChanged(ITable* table, const std::string& key, EntryValue value, bool isNew)
584{
585 if (key==kSaveField)
586 {
587 if (table->GetBoolean(kSaveField, false))
588 Save();
589 }
590 else
591 {
592 Synchronized sync(m_tableLock);
593
594 if (!isKeyAcceptable(key) || table->GetString(key, "").find('"')!=std::string::npos)
595 {
596 if(m_values.find(key) != m_values.end()){
597 m_values.erase(key);
598 std::vector<std::string>::iterator it = m_keys.begin();
599 for (; it != m_keys.end(); it++)
600 {
601 if (key==*it)
602 {
603 m_keys.erase(it);
604 break;
605 }
606 }
607 table->PutString(key, "\"");
608 }
609 }
610 else
611 {
612 std::pair<StringMap::iterator, bool> ret =
613 m_values.insert(StringMap::value_type(key, table->GetString(key, "")));
614 if (ret.second)
615 m_keys.push_back(key);
616 else
617 ret.first->second = table->GetString(key, "");
618 }
619 }
620}