Merge branch 'refactor'
[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 }
20
21 /**
22  * Initialise a DataFile from a filename; opens read/write FILE*
23  * @param df - DataFile to initialise
24  * @param filename - Name of file; overwritten if it exists
25  */
26 void Data_Open(DataFile * df, const char * filename)
27 {
28         assert(filename != NULL);
29         assert(df != NULL);
30
31         // Set the filename
32         df->filename = filename;
33
34         // Set number of DataPoints
35         df->num_points = 0; 
36
37         // Set write FILE*
38         df->write_file = fopen(filename, "w");
39         if (df->write_file == NULL)
40         {
41                 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
42         }
43         
44         // Set read FILE*
45         df->read_file = fopen(filename, "r");
46         if (df->read_file == NULL)
47         {
48                 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
49         }
50
51
52 }
53
54 /**
55  * Close a DataFile
56  * @param df - The DataFile to close
57  */
58 void Data_Close(DataFile * df)
59 {
60         assert(df != NULL);
61
62         //TODO: Write data to TSV?
63
64         // Clear the FILE*s
65         df->read_file = NULL;
66         df->write_file = NULL;
67
68         // Clear the filename
69         df->filename = NULL;
70 }
71
72 /**
73  * Save DataPoints to a DataFile
74  * @param df - The DataFile to save to
75  * @param buffer - Array of DataPoint(s) to save
76  * @param amount - Number of DataPoints in the buffer
77  */
78 void Data_Save(DataFile * df, DataPoint * buffer, int amount)
79 {
80         assert(df != NULL);
81         assert(buffer != NULL);
82         assert(amount >= 0);
83
84         // Go to the end of the file
85         if (fseek(df->write_file, 0, SEEK_END) < 0)
86         {
87                 Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno));
88         }
89
90         // Attempt to write the DataPoints
91         int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->write_file);
92         
93         // Check if the correct number of points were written
94         if (amount_written != amount)
95         {
96                 Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno));
97         }
98
99         // Update number of DataPoints
100         pthread_mutex_lock(&(df->mutex));
101                 df->num_points += amount_written;
102         pthread_mutex_unlock(&(df->mutex));
103         
104 }
105
106 /**
107  * Read DataPoints from a DataFile
108  * @param df - The DataFile to read from
109  * @param buffer - Array to fill with DataPoints
110  * @param index - Index to start reading at (inclusive)
111  * @param amount - Maximum number of DataPoints to read
112  * @returns - Actual number of points read (If smaller than amount, the end of the file was reached)
113  */
114 int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
115 {
116         assert(df != NULL);
117         assert(buffer != NULL);
118         assert(index >= 0);
119         assert(amount > 0);
120         
121         // If we would read past the end of the file, reduce the amount of points to read
122         pthread_mutex_lock(&(df->mutex));
123                 if (index + amount >= df->num_points)
124                 {
125                         Log(LOGDEBUG, "Requested %d points but will only read %d to get to EOF", amount, df->num_points - index);
126                         amount = df->num_points - index;
127                 }
128         pthread_mutex_unlock(&(df->mutex));
129
130         // Go to position in file
131         if (fseek(df->read_file, index*sizeof(DataPoint), SEEK_SET))
132         {
133                 Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno));
134         }
135
136         // Attempt to read the DataPoints
137         int amount_read = fread(buffer, sizeof(DataPoint), amount, df->read_file);
138
139         // Check if correct number of points were read
140         if (amount_read != amount)
141         {
142                 Fatal("Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno));
143         }
144
145         return amount;
146 }
147
148 /**
149  * Print data points between two indexes using a given format
150  * @param df - DataFile to print
151  * @param start_index - Index to start at (inclusive)
152  * @param end_index - Index to end at (inclusive)
153  * @param format - The format to use
154  */
155 void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
156 {
157         assert(df != NULL);
158         assert(start_index >= 0);
159         assert(end_index <= df->num_points-1);
160
161         const char * fmt_string; // Format for each data point
162         char seperator; // Character used to seperate successive data points
163         
164         // Determine what format string and seperator character to use
165         switch (format)
166         {
167                 case JSON:
168                         fmt_string = "[%f,%f]";
169                         seperator = ',';
170                         // For JSON we need an opening bracket
171                         FCGI_PrintRaw("["); 
172                         break;
173                 case TSV:
174                         fmt_string = "%f\t%f";
175                         seperator = '\n';
176                         break;
177         }
178
179         DataPoint buffer[DATA_BUFSIZ]; // Buffer
180
181         int index = start_index;
182
183         // Repeat until all DataPoints are printed
184         while (index <= end_index)
185         {
186                 // Fill the buffer from the DataFile
187                 int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ);
188
189                 // Print all points in the buffer
190                 for (int i = 0; i < amount_read; ++i)
191                 {
192                         // Print individual DataPoint
193                         FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value);
194                         
195                         // Last seperator is not required
196                         if (index+1 < end_index)
197                                 FCGI_PrintRaw("%c", seperator);
198
199                         // Advance the position in the DataFile
200                         ++index;
201                 }
202         }
203         
204         switch (format)
205         {
206                 case JSON:
207                         // For JSON we need a closing bracket
208                         FCGI_PrintRaw("]"); 
209                         break;
210                 default:
211                         break;
212         }
213 }
214
215 /**
216  * Print data points between two time stamps using a given format.
217  * Prints nothing if the time stamp
218  * @param df - DataFile to print
219  * @param start_time - Time to start from (inclusive)
220  * @param end_time - Time to end at (inclusive)
221  * @param format - The format to use
222  */
223 void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
224 {
225         assert(df != NULL);
226         assert(start_time > 0);
227         assert(end_time > 0);
228         assert(end_time > start_time);
229         
230         DataPoint closest;
231
232         // Get starting index
233         int start_index = Data_FindByTime(df, start_time, &closest);
234
235         // Start time is greater than most recent time stamp
236         if (start_index >= df->num_points-1 && closest.time_stamp < start_time)
237         {
238                 Data_PrintByIndexes(df, 0, 0, format); // Will print "empty" dataset
239                 return;
240         }
241
242         // Get finishing index
243         int end_index = Data_FindByTime(df, end_time, &closest);
244
245         // Print data between the indexes
246         Data_PrintByIndexes(df, start_index, end_index, format);
247 }
248
249 /**
250  * Get the index of the DataPoint closest to a given time stamp
251  * @param df - DataFile to search
252  * @param time_stamp - The time stamp to search for
253  * @param closest - If not NULL, will be filled with the DataPoint chosen
254  * @returns index of DataPoint with the *closest* time stamp to that given
255  */
256 int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest)
257 {
258         assert(df != NULL);
259         assert(time_stamp >= 0);
260         assert(closest != NULL);
261
262         DataPoint tmp; // Current DataPoint in binary search
263
264         int lower = 0; // lower index in binary search
265         pthread_mutex_lock(&(df->mutex));
266                 int upper = df->num_points - 1; // upper index in binary search
267         pthread_mutex_unlock(&(df->mutex));
268         int index = 0; // current index in binary search
269
270         // Commence binary search:
271         while (upper - lower > 1)
272         {
273                 // Pick midpoint
274                 index = lower + ((upper - lower)/2);
275
276                 // Look at DataPoint
277                 if (Data_Read(df, &tmp, index, 1) != 1)
278                 {
279                         Fatal("Couldn't read DataFile %s at index %d", df->filename, index);
280                 }
281
282                 // Change search interval to either half appropriately
283                 if (tmp.time_stamp > time_stamp)
284                 {
285                         upper = index;
286                 }
287                 else if (tmp.time_stamp < time_stamp)
288                 {
289                         lower = index;
290                 }
291         }
292
293         // Store closest DataPoint
294         if (closest != NULL)
295                 *closest = tmp;
296         
297         return index;
298         
299 }

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