Add Actuator related code
[matches/MCTX3420.git] / server / data.c
1 /**
2  * @file data.c
3  * @purpose Implementation of data handling functions; saving, loading, displaying, selecting.
4  */
5
6 #include "data.h"
7 #include <assert.h> //TODO: Remove asserts
8
9 /**
10  * One off initialisation of DataFile
11  * @param df - The DataFile
12  */
13 void Data_Init(DataFile * df)
14 {
15         // Everything is NULL
16         df->filename = NULL;
17         df->read_file = NULL;
18         df->write_file = NULL;
19         pthread_mutex_init(&(df->mutex), NULL);
20 }
21
22 /**
23  * Initialise a DataFile from a filename; opens read/write FILE*
24  * @param df - DataFile to initialise
25  * @param filename - Name of file; overwritten if it exists
26  */
27 void Data_Open(DataFile * df, const char * filename)
28 {
29         assert(filename != NULL);
30         assert(df != NULL);
31
32         // Set the filename
33         df->filename = strdup(filename);
34
35         // Set number of DataPoints
36         df->num_points = 0; 
37
38         // Set write FILE*
39         df->write_file = fopen(filename, "wb+");
40         if (df->write_file == NULL)
41         {
42                 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
43         }
44         
45         // Set read FILE*
46         df->read_file = df->write_file;
47
48         //NOTE: Opening the same file in read mode gives funny results; fread generally reads less than expected
49         // The strerror is: "Transport endpoint is not connected"
50         /*
51         df->read_file = fopen(filename, "rb");
52         if (df->read_file == NULL)
53         {
54                 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
55         }
56         */
57
58 }
59
60 /**
61  * Close a DataFile
62  * @param df - The DataFile to close
63  */
64 void Data_Close(DataFile * df)
65 {
66         assert(df != NULL);
67
68         //TODO: Write data to TSV?
69
70         // Clear the FILE*s
71         df->read_file = NULL;
72         df->write_file = NULL;
73
74         fclose(df->write_file);
75
76         // Clear the filename
77         free(df->filename);
78         df->filename = NULL;
79 }
80
81 /**
82  * Save DataPoints to a DataFile
83  * @param df - The DataFile to save to
84  * @param buffer - Array of DataPoint(s) to save
85  * @param amount - Number of DataPoints in the buffer
86  */
87 void Data_Save(DataFile * df, DataPoint * buffer, int amount)
88 {
89         pthread_mutex_unlock(&(df->mutex));
90         assert(df != NULL);
91         assert(buffer != NULL);
92         assert(amount >= 0);
93
94         // Go to the end of the file
95         if (fseek(df->write_file, 0, SEEK_END) < 0)
96         {
97                 Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno));
98         }
99
100         // Attempt to write the DataPoints
101         int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->write_file);
102         
103         // Check if the correct number of points were written
104         if (amount_written != amount)
105         {
106                 Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno));
107         }
108
109         // Update number of DataPoints
110         df->num_points += amount_written;
111
112         pthread_mutex_unlock(&(df->mutex));
113         
114 }
115
116 /**
117  * Read DataPoints from a DataFile
118  * @param df - The DataFile to read from
119  * @param buffer - Array to fill with DataPoints
120  * @param index - Index to start reading at (inclusive)
121  * @param amount - Maximum number of DataPoints to read
122  * @returns - Actual number of points read (If smaller than amount, the end of the file was reached)
123  */
124 int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
125 {
126         pthread_mutex_lock(&(df->mutex));
127
128         assert(df != NULL);
129         assert(buffer != NULL);
130         assert(index >= 0);
131         assert(amount > 0);
132         
133         // If we would read past the end of the file, reduce the amount of points to read
134         
135                 if (index + amount > df->num_points)
136                 {
137                         Log(LOGDEBUG, "Requested %d points but will only read %d to get to EOF (%d)", amount, df->num_points - index, df->num_points);
138                         amount = df->num_points - index;
139                 }
140         
141
142         // Go to position in file
143         if (fseek(df->read_file, index*sizeof(DataPoint), SEEK_SET))
144         {
145                 Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno));
146         }
147
148         // Attempt to read the DataPoints
149         int amount_read = fread(buffer, sizeof(DataPoint), amount, df->read_file);
150
151         // Check if correct number of points were read
152         if (amount_read != amount)
153         {
154                 Log(LOGERR,"Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno));
155         }
156
157         pthread_mutex_unlock(&(df->mutex));
158         return amount_read;
159 }
160
161 /**
162  * Print data points between two indexes using a given format
163  * @param df - DataFile to print
164  * @param start_index - Index to start at (inclusive)
165  * @param end_index - Index to end at (inclusive)
166  * @param format - The format to use
167  */
168 void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
169 {
170         assert(df != NULL);
171         assert(start_index >= 0);
172         assert(end_index >= 0);
173         assert(end_index <= df->num_points-1 || df->num_points == 0);
174
175         const char * fmt_string; // Format for each data point
176         char seperator; // Character used to seperate successive data points
177         
178         // Determine what format string and seperator character to use
179         switch (format)
180         {
181                 case JSON:
182                         fmt_string = "[%f,%f]";
183                         seperator = ',';
184                         // For JSON we need an opening bracket
185                         FCGI_PrintRaw("["); 
186                         break;
187                 case TSV:
188                         fmt_string = "%f\t%f";
189                         seperator = '\n';
190                         break;
191         }
192
193         DataPoint buffer[DATA_BUFSIZ]; // Buffer
194         // initialise buffer to stop stuff complaining
195         memset(buffer, 0, sizeof(DataPoint)*DATA_BUFSIZ);
196         
197         if (start_index < end_index)
198         {
199
200                 int index = start_index;
201                 // Repeat until all DataPoints are printed
202                 while (index <= end_index)
203                 {
204                         // Fill the buffer from the DataFile
205                         int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ);
206         
207                         // Print all points in the buffer
208                         for (int i = 0; i < amount_read && index <= end_index; ++i)
209                         {
210                                 // Print individual DataPoint
211                                 FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value);
212                                 
213                                 // Last seperator is not required
214                                 if (index+1 <= end_index)
215                                         FCGI_PrintRaw("%c", seperator);
216         
217                                 // Advance the position in the DataFile
218                                 ++index;
219                         }
220                 }
221         }
222         
223         switch (format)
224         {
225                 case JSON:
226                         // For JSON we need a closing bracket
227                         FCGI_PrintRaw("]"); 
228                         break;
229                 default:
230                         break;
231         }
232 }
233
234 /**
235  * Print data points between two time stamps using a given format.
236  * Prints nothing if the time stamp
237  * @param df - DataFile to print
238  * @param start_time - Time to start from (inclusive)
239  * @param end_time - Time to end at (inclusive)
240  * @param format - The format to use
241  */
242 void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
243 {
244         assert(df != NULL);
245         assert(start_time >= 0);
246         assert(end_time >= 0);
247         assert(end_time >= start_time);
248         
249         DataPoint closest;
250
251         // Get starting index
252         int start_index = Data_FindByTime(df, start_time, &closest);
253
254         // Start time is greater than most recent time stamp
255         if (start_index >= df->num_points-1)
256         {
257                 if (start_index == 0 || closest.time_stamp < start_time)
258                 {
259                         Data_PrintByIndexes(df, 0, 0, format); // Will print "empty" dataset
260                         return;
261                 }
262         }
263
264         // Get finishing index
265         int end_index = Data_FindByTime(df, end_time, &closest);
266
267         // Print data between the indexes
268         Data_PrintByIndexes(df, start_index, end_index, format);
269 }
270
271 /**
272  * Get the index of the DataPoint closest to a given time stamp
273  * @param df - DataFile to search
274  * @param time_stamp - The time stamp to search for
275  * @param closest - If not NULL, will be filled with the DataPoint chosen
276  * @returns index of DataPoint with the *closest* time stamp to that given
277  */
278 int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest)
279 {
280         assert(df != NULL);
281         assert(time_stamp >= 0);
282         assert(closest != NULL);
283
284         DataPoint tmp; // Current DataPoint in binary search
285
286         int lower = 0; // lower index in binary search
287         pthread_mutex_lock(&(df->mutex));
288                 int upper = df->num_points - 1; // upper index in binary search
289         pthread_mutex_unlock(&(df->mutex));
290         int index = 0; // current index in binary search
291
292         // Commence binary search:
293         while (upper - lower > 1)
294         {
295                 // Pick midpoint
296                 index = lower + ((upper - lower)/2);
297
298                 // Look at DataPoint
299                 if (Data_Read(df, &tmp, index, 1) != 1)
300                 {
301                         Fatal("Couldn't read DataFile %s at index %d", df->filename, index);
302                 }
303
304                 // Change search interval to either half appropriately
305                 if (tmp.time_stamp > time_stamp)
306                 {
307                         upper = index;
308                 }
309                 else if (tmp.time_stamp < time_stamp)
310                 {
311                         lower = index;
312                 }
313         }
314
315         // Store closest DataPoint
316         if (closest != NULL)
317                 *closest = tmp;
318         
319         return index;
320         
321 }
322
323 /**
324  * Helper; handle FCGI response that requires data
325  * Should be called first.
326  * @param df - DataFile to access
327  * @param start - Info about start_time param 
328  * @param end - Info about end_time param
329  * @param fmt - Info about format param
330  * @param current_time - Current time
331  */
332 void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time)
333 {
334         double start_time = *(double*)(start->value);
335         double end_time = *(double*)(end->value);
336
337         if (format == JSON)
338         {
339                 FCGI_JSONKey("data");
340         }
341
342         // If a time was specified
343         if (FCGI_RECEIVED(start->flags) || FCGI_RECEIVED(end->flags))
344         {
345                 // Wrap times relative to the current time
346                 if (start_time < 0)
347                         start_time += current_time;
348                 if (end_time < 0)
349                         end_time += current_time;
350
351                 // Print points by time range
352                 Data_PrintByTimes(df, start_time, end_time, format);
353
354         }
355         else // No time was specified; just return a recent set of points
356         {
357                 pthread_mutex_lock(&(df->mutex));
358                         int start_index = df->num_points-DATA_BUFSIZ;
359                         int end_index = df->num_points-1;
360                 pthread_mutex_unlock(&(df->mutex));
361
362                 // Bounds check
363                 if (start_index < 0)
364                         start_index = 0;
365                 if (end_index < 0)
366                         end_index = 0;
367
368                 // Print points by indexes
369                 Data_PrintByIndexes(df, start_index, end_index, format);
370         }
371
372 }
373
374 /**
375  * Helper - Convert human readable format string to DataFormat
376  * @param fmt - FCGIValue to use
377  */
378 DataFormat Data_GetFormat(FCGIValue * fmt)
379 {
380         char * fmt_str = *(char**)(fmt->value);
381         // Check if format type was specified
382         if (FCGI_RECEIVED(fmt->flags))
383         {
384                 if (strcmp(fmt_str, "json") == 0)
385                         return JSON;
386                 else if (strcmp(fmt_str, "tsv") == 0)
387                         return TSV;
388                 else
389                         Log(LOGERR, "Unknown format type \"%s\"", fmt_str);
390         }
391         return JSON;
392 }

UCC git Repository :: git.ucc.asn.au