blob: 7a3d9eab6c6a83cadfb9b0786b8571edda38163f [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
jerrymf1579332013-02-07 01:56:28 +000047 nUsageReporting::report(nUsageReporting::kResourceType_Preferences, 0);
48}
49
50Preferences::~Preferences()
51{
52 semTake(m_tableLock, WAIT_FOREVER);
53 semDelete(m_tableLock);
54 semTake(m_fileLock, WAIT_FOREVER);
55 semDelete(m_fileOpStarted);
56 semDelete(m_fileLock);
57}
58
59/**
60 * Get the one and only {@link Preferences} object
61 * @return pointer to the {@link Preferences}
62 */
63Preferences *Preferences::GetInstance()
64{
65 if (_instance == NULL)
66 _instance = new Preferences;
67 return _instance;
68}
69
70/**
71 * Returns a vector of all the keys
72 * @return a vector of the keys
73 */
74std::vector<std::string> Preferences::GetKeys()
75{
76 return m_keys;
77}
78
79/**
80 * Returns the string at the given key. If this table does not have a value
81 * for that position, then the given defaultValue will be returned.
82 * @param key the key
83 * @param defaultValue the value to return if none exists in the table
84 * @return either the value in the table, or the defaultValue
85 */
86std::string Preferences::GetString(const char *key, const char *defaultValue)
87{
88 std::string value = Get(key);
89 return value.empty() ? defaultValue : value;
90}
91
92/**
93 * Returns the string at the given key. If this table does not have a value
94 * for that position, then the given defaultValue will be returned.
95 * @param key the key
96 * @param value the buffer to copy the value into
97 * @param valueSize the size of value
98 * @param defaultValue the value to return if none exists in the table
99 * @return The size of the returned string
100 */
101int Preferences::GetString(const char *key, char *value, int valueSize, const char *defaultValue)
102{
103 std::string stringValue = GetString(key, defaultValue);
104 stringValue.copy(value, valueSize);
105 return stringValue.size();
106}
107
108/**
109 * Returns the int at the given key. If this table does not have a value
110 * for that position, then the given defaultValue value will be returned.
111 * @param key the key
112 * @param defaultValue the value to return if none exists in the table
113 * @return either the value in the table, or the defaultValue
114 */
115int Preferences::GetInt(const char *key, int defaultValue)
116{
117 std::string value = Get(key);
118 if (value.empty())
119 return defaultValue;
120
121 return strtol(value.c_str(), NULL, 0);
122}
123
124/**
125 * Returns the double at the given key. If this table does not have a value
126 * for that position, then the given defaultValue value will be returned.
127 * @param key the key
128 * @param defaultValue the value to return if none exists in the table
129 * @return either the value in the table, or the defaultValue
130 */
131double Preferences::GetDouble(const char *key, double defaultValue)
132{
133 std::string value = Get(key);
134 if (value.empty())
135 return defaultValue;
136
137 return strtod(value.c_str(), NULL);
138}
139
140/**
141 * Returns the float at the given key. If this table does not have a value
142 * for that position, then the given defaultValue value will be returned.
143 * @param key the key
144 * @param defaultValue the value to return if none exists in the table
145 * @return either the value in the table, or the defaultValue
146 */
147float Preferences::GetFloat(const char *key, float defaultValue)
148{
149 std::string value = Get(key);
150 if (value.empty())
151 return defaultValue;
152
153 return strtod(value.c_str(), NULL);
154}
155
156/**
157 * Returns the boolean at the given key. If this table does not have a value
158 * for that position, then the given defaultValue value will be returned.
159 * @param key the key
160 * @param defaultValue the value to return if none exists in the table
161 * @return either the value in the table, or the defaultValue
162 */
163bool Preferences::GetBoolean(const char *key, bool defaultValue)
164{
165 std::string value = Get(key);
166 if (value.empty())
167 return defaultValue;
168
169 if (value.compare("true") == 0)
170 return true;
171 else if (value.compare("false") == 0)
172 return false;
173
174 wpi_setWPIErrorWithContext(ParameterOutOfRange, "Boolean value does not contain \"true\" or \"false\"");
175 return false;
176}
177
178/**
179 * Returns the long (INT64) at the given key. If this table does not have a value
180 * for that position, then the given defaultValue value will be returned.
181 * @param key the key
182 * @param defaultValue the value to return if none exists in the table
183 * @return either the value in the table, or the defaultValue
184 */
185INT64 Preferences::GetLong(const char *key, INT64 defaultValue)
186{
187 std::string value = Get(key);
188 if (value.empty())
189 return defaultValue;
190
191 // Ummm... not available in our VxWorks...
192 //return strtoll(value.c_str(), NULL, 0);
193 INT64 intVal;
194 sscanf(value.c_str(), "%lld", &intVal);
195 return intVal;
196}
197
198/**
199 * Puts the given string into the preferences table.
200 *
201 * <p>The value may not have quotation marks, nor may the key
202 * have any whitespace nor an equals sign</p>
203 *
204 * <p>This will <b>NOT</b> save the value to memory between power cycles,
205 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care).
206 * at some point after calling this.</p>
207 * @param key the key
208 * @param value the value
209 */
210void Preferences::PutString(const char *key, const char *value)
211{
212 if (value == NULL)
213 {
214 wpi_setWPIErrorWithContext(NullParameter, "value");
215 return;
216 }
217 if (std::string(value).find_first_of("\"") != std::string::npos)
218 {
219 wpi_setWPIErrorWithContext(ParameterOutOfRange, "value contains illegal characters");
220 return;
221 }
222 Put(key, value);
223}
224
225/**
226 * Puts the given int into the preferences table.
227 *
228 * <p>The key may not have any whitespace nor an equals sign</p>
229 *
230 * <p>This will <b>NOT</b> save the value to memory between power cycles,
231 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
232 * at some point after calling this.</p>
233 * @param key the key
234 * @param value the value
235 */
236void Preferences::PutInt(const char *key, int value)
237{
238 char buf[32];
239 snprintf(buf, 32, "%d", value);
240 Put(key, buf);
241}
242
243/**
244 * Puts the given double into the preferences table.
245 *
246 * <p>The key may not have any whitespace nor an equals sign</p>
247 *
248 * <p>This will <b>NOT</b> save the value to memory between power cycles,
249 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
250 * at some point after calling this.</p>
251 * @param key the key
252 * @param value the value
253 */
254void Preferences::PutDouble(const char *key, double value)
255{
256 char buf[32];
257 snprintf(buf, 32, "%f", value);
258 Put(key, buf);
259}
260
261/**
262 * Puts the given float into the preferences table.
263 *
264 * <p>The key may not have any whitespace nor an equals sign</p>
265 *
266 * <p>This will <b>NOT</b> save the value to memory between power cycles,
267 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
268 * at some point after calling this.</p>
269 * @param key the key
270 * @param value the value
271 */
272void Preferences::PutFloat(const char *key, float value)
273{
274 char buf[32];
275 snprintf(buf, 32, "%f", value);
276 Put(key, buf);
277}
278
279/**
280 * Puts the given boolean into the preferences table.
281 *
282 * <p>The key may not have any whitespace nor an equals sign</p>
283 *
284 * <p>This will <b>NOT</b> save the value to memory between power cycles,
285 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
286 * at some point after calling this.</p>
287 * @param key the key
288 * @param value the value
289 */
290void Preferences::PutBoolean(const char *key, bool value)
291{
292 Put(key, value ? "true" : "false");
293}
294
295/**
296 * Puts the given long (INT64) into the preferences table.
297 *
298 * <p>The key may not have any whitespace nor an equals sign</p>
299 *
300 * <p>This will <b>NOT</b> save the value to memory between power cycles,
301 * to do that you must call {@link Preferences#Save() Save()} (which must be used with care)
302 * at some point after calling this.</p>
303 * @param key the key
304 * @param value the value
305 */
306void Preferences::PutLong(const char *key, INT64 value)
307{
308 char buf[32];
309 snprintf(buf, 32, "%lld", value);
310 Put(key, buf);
311}
312
313/**
314 * Saves the preferences to a file on the cRIO.
315 *
316 * <p>This should <b>NOT</b> be called often.
317 * Too many writes can damage the cRIO's flash memory.
318 * While it is ok to save once or twice a match, this should never
319 * be called every run of {@link IterativeRobot#TeleopPeriodic()}, etc.</p>
320 *
321 * <p>The actual writing of the file is done in a separate thread.
322 * However, any call to a get or put method will wait until the table is fully saved before continuing.</p>
323 */
324void Preferences::Save()
325{
326 Synchronized sync(m_fileLock);
327 m_writeTask.Start((UINT32)this);
328 semTake(m_fileOpStarted, WAIT_FOREVER);
329}
330
331/**
332 * Returns whether or not there is a key with the given name.
333 * @param key the key
334 * @return if there is a value at the given key
335 */
336bool Preferences::ContainsKey(const char *key)
337{
338 return !Get(key).empty();
339}
340
341/**
342 * Remove a preference
343 * @param key the key
344 */
345void Preferences::Remove(const char *key)
346{
347 m_values.erase(std::string(key));
348 std::vector<std::string>::iterator it = m_keys.begin();
349 for (; it != m_keys.end(); it++)
350 {
351 if (it->compare(key) == 0)
352 {
353 m_keys.erase(it);
354 break;
355 }
356 }
357}
358
359/**
360 * Returns the value at the given key.
361 * @param key the key
362 * @return the value (or empty if none exists)
363 */
364std::string Preferences::Get(const char *key)
365{
366 Synchronized sync(m_tableLock);
367 if (key == NULL)
368 {
369 wpi_setWPIErrorWithContext(NullParameter, "key");
370 return std::string("");
371 }
372 return m_values[std::string(key)];
373}
374
375/**
376 * Puts the given value into the given key position
377 * @param key the key
378 * @param value the value
379 */
380void Preferences::Put(const char *key, std::string value)
381{
382 Synchronized sync(m_tableLock);
383 if (key == NULL)
384 {
385 wpi_setWPIErrorWithContext(NullParameter, "key");
386 return;
387 }
388
389 if (std::string(key).find_first_of("=\n\r \t\"") != std::string::npos)
390 {
391 wpi_setWPIErrorWithContext(ParameterOutOfRange, "key contains illegal characters");
392 return;
393 }
394
395 std::pair<StringMap::iterator, bool> ret =
396 m_values.insert(StringMap::value_type(key, value));
397 if (ret.second)
398 m_keys.push_back(key);
399 else
400 ret.first->second = value;
401
402 NetworkTable::GetTable(kTableName)->PutString(key, value);
403}
404
405/**
406 * The internal method to read from a file.
407 * This will be called in its own thread when the preferences singleton is
408 * first created.
409 */
410void Preferences::ReadTaskRun()
411{
412 Synchronized sync(m_tableLock);
413 semGive(m_fileOpStarted);
414
415 std::string comment;
416
417 FILE *file = NULL;
418 file = fopen(kFileName, "r");
419
420 if (file != NULL)
421 {
422 std::string buffer;
423 while (true)
424 {
425 char value;
426 do
427 {
428 value = fgetc(file);
429 } while (value == ' ' || value == '\t');
jerrym42dedc02013-02-25 01:59:14 +0000430
jerrymf1579332013-02-07 01:56:28 +0000431 if (value == '\n' || value == ';')
432 {
433 if (value == '\n')
434 {
435 comment += "\n";
436 }
437 else
438 {
439 buffer.clear();
440 for (; value != '\n' && !feof(file); value = fgetc(file))
441 buffer += value;
442 buffer += '\n';
443 comment += buffer;
444 }
445 }
446 else if (value == '[')
447 {
448 // Find the end of the section and the new line after it and throw it away
449 for (; value != ']' && !feof(file); value = fgetc(file));
450 for (; value != '\n' && !feof(file); value = fgetc(file));
451 }
452 else
453 {
454 buffer.clear();
455 for (; value != '=' && !feof(file); )
456 {
457 buffer += value;
458 do
459 {
460 value = fgetc(file);
461 } while (value == ' ' || value == '\t');
462 }
463 std::string name = buffer;
464 buffer.clear();
465
466 bool shouldBreak = false;
467
468 do
469 {
470 value = fgetc(file);
471 } while (value == ' ' || value == '\t');
472
473 if (value == '"')
474 {
475 for (value = fgetc(file); value != '"' && !feof(file); value = fgetc(file))
476 buffer += value;
477
478 // Clear the line
479 while (fgetc(file) != '\n' && !feof(file));
480 }
481 else
482 {
483 for (; value != '\n' && !feof(file);)
484 {
485 buffer += value;
486 do
487 {
488 value = fgetc(file);
489 } while (value == ' ' || value == '\t');
490 }
491 if (feof(file))
492 shouldBreak = true;
493 }
494
495 std::string value = buffer;
496
497 if (!name.empty() && !value.empty())
498 {
499 m_keys.push_back(name);
500 m_values.insert(std::pair<std::string, std::string>(name, value));
jerrym42dedc02013-02-25 01:59:14 +0000501 NetworkTable::GetTable(kTableName)->PutString(name, value);
jerrymf1579332013-02-07 01:56:28 +0000502
503 if (!comment.empty())
504 {
505 m_comments.insert(std::pair<std::string, std::string>(name, comment));
506 comment.clear();
507 }
508 }
509
510 if (shouldBreak)
511 break;
512 }
513 }
514 }
515 else
516 {
517 wpi_setWPIErrorWithContext(NoAvailableResources, "Failed to open preferences file.");
518 }
519
520 if (file != NULL)
521 fclose(file);
522
523 if (!comment.empty())
524 m_endComment = comment;
jerrym42dedc02013-02-25 01:59:14 +0000525
526 NetworkTable::GetTable(kTableName)->PutBoolean(kSaveField, false);
527 NetworkTable::GetTable(kTableName)->AddTableListener(this);
jerrymf1579332013-02-07 01:56:28 +0000528}
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}