1 #include <limits>
2 #include <cassert>
3 #include <cstring>
4 #include <algorithm>
5 #include <set>
6 
7 #include "../util.h"
8 #include <rtosc/arg-val-cmp.h>
9 #include <rtosc/pretty-format.h>
10 #include <rtosc/bundle-foreach.h>
11 #include <rtosc/ports.h>
12 #include <rtosc/ports-runtime.h>
13 #include <rtosc/default-value.h>
14 #include <rtosc/savefile.h>
15 
16 namespace rtosc {
17 
18 namespace {
19     constexpr std::size_t buffersize = 8192;
20     constexpr size_t max_arg_vals = 2048;
21 }
22 
23 // this basically does:
24 // walk over all ports and for each port:
25 // * get default value
26 // * get current value
27 // * compare: if values are different -> write to savefile
get_changed_values(const Ports & ports,void * runtime)28 std::string get_changed_values(const Ports& ports, void* runtime)
29 {
30     char port_buffer[buffersize];
31     memset(port_buffer, 0, buffersize); // requirement for walk_ports
32 
33     struct data_t
34     {
35         std::string res;
36         std::set<std::string> written;
37     } data;
38 
39     auto on_reach_port =
40             [](const Port* p, const char* port_buffer,
41                const char* port_from_base, const Ports& base,
42                void* data, void* runtime)
43     {
44         assert(runtime);
45         const Port::MetaContainer meta = p->meta();
46 #if 0
47 // practical for debugging if a parameter was changed, but not saved
48         const char* cmp = "/part15/kit0/adpars/GlobalPar/Reson/Prespoints";
49         if(!strncmp(port_buffer, cmp, strlen(cmp)))
50         {
51             puts("break here");
52         }
53 #endif
54 
55         if((p->name[strlen(p->name)-1] != ':' && !strstr(p->name, "::"))
56             || meta.find("parameter") == meta.end())
57         {
58             // runtime information can not be retrieved,
59             // thus, it can not be compared with the default value
60             return;
61         }
62         else
63         { // TODO: duplicate to above? (colon[1])
64             const char* colon = strchr(p->name, ':');
65             if(!colon || !colon[1])
66             {
67                 // runtime information can not be loaded, so don't save it
68                 // a possible FEATURE would be to save it anyways
69                 return;
70             }
71         }
72 
73         if(meta.find("alias") != meta.end())
74         {
75             // this param is already saved in another port
76             return;
77         }
78 
79         {
80             std::set<std::string>& written = ((data_t*)data)->written;
81             if(written.find(port_buffer) != written.end()) { return; }
82             else { written.insert(port_buffer); }
83         }
84 
85         char loc[buffersize] = ""; // buffer to hold the dispatched path
86         rtosc_arg_val_t arg_vals_default[max_arg_vals];
87         rtosc_arg_val_t arg_vals_runtime[max_arg_vals];
88         // buffer to hold the message (i.e. /port ..., without port's bases)
89         char buffer_with_port[buffersize];
90         char strbuf[buffersize]; // temporary string buffer for pretty-printing
91 
92         std::string* res = &((data_t*)data)->res;
93         assert(strlen(port_buffer) + 1 < buffersize);
94         // copy the path until before the message
95         fast_strcpy(loc, port_buffer, std::min((ptrdiff_t)buffersize,
96                                                port_from_base - port_buffer + 1
97                                                ));
98         char* loc_end = loc + (port_from_base - port_buffer);
99         size_t loc_remain_size = buffersize - (port_from_base - port_buffer);
100         *loc_end = 0;
101 
102         const char* portargs = strchr(p->name, ':');
103         if(!portargs)
104             portargs = p->name + strlen(p->name);
105 
106 #if 0 // debugging stuff
107         if(!strncmp(port_buffer, "/part1/Penabled", 5) &&
108            !strncmp(port_buffer+6, "/Penabled", 9))
109         {
110             printf("runtime: %ld\n", (long int)runtime);
111         }
112 #endif
113 // TODO: p->name: duplicate to p
114         int nargs_default = get_default_value(p->name,
115                                               portargs,
116                                               base,
117                                               runtime,
118                                               p,
119                                               -1,
120                                               max_arg_vals,
121                                               arg_vals_default,
122                                               strbuf,
123                                               buffersize);
124 
125         if(nargs_default > 0)
126         {
127             size_t nargs_runtime = 0;
128 
129             auto ftor = [&](const Port* p, const char* ,
130                             const char* old_end,
131                             const Ports& ,void* ,void* runtime)
132             {
133                 fast_strcpy(buffer_with_port, p->name, buffersize);
134 
135                 // the caller of ftor (in some cases bundle_foreach) has
136                 // already filled old_end correctly, but we have to copy this
137                 // over to loc_end
138                 fast_strcpy(loc_end, old_end, loc_remain_size);
139 
140                 size_t nargs_runtime_cur =
141                     helpers::get_value_from_runtime(runtime, *p,
142                                                     buffersize, loc, old_end,
143                                                     buffer_with_port,
144                                                     buffersize,
145                                                     max_arg_vals,
146                                                     arg_vals_runtime +
147                                                         nargs_runtime);
148                 nargs_runtime += nargs_runtime_cur;
149             };
150 
151             auto refix_old_end = [&base](const Port* _p, char* _old_end)
152             { // TODO: remove base capture
153                 bundle_foreach(*_p, _p->name, _old_end, NULL,
154                                base, NULL, NULL, bundle_foreach_do_nothing,
155                                false, false);
156             };
157 
158             if(strchr(p->name, '#'))
159             {
160                 // idea:
161                 //                    p/a/b
162                 // bundle_foreach =>  p/a#0/b, p/a#1/b, ... p/a#n/b, p
163                 // bundle_foreach =>  p/a/b
164                 // => justification for const_cast
165 
166                 // Skip the array element (type 'a') for now...
167                 ++nargs_runtime;
168 
169                 // Start filling at arg_vals_runtime + 1
170                 char* old_end_noconst = const_cast<char*>(port_from_base);
171                 bundle_foreach(*p, p->name, old_end_noconst, port_buffer + 1,
172                                base, data, runtime,
173                                ftor, true);
174 
175                 // glue the old end behind old_end_noconst again
176                 refix_old_end(p, old_end_noconst);
177 
178                 // "Go back" to fill arg_vals_runtime + 0
179                 arg_vals_runtime[0].type = 'a';
180                 arg_vals_runtime[0].val.a.len = nargs_runtime-1;
181                 arg_vals_runtime[0].val.a.type = arg_vals_runtime[1].type;
182             }
183             else
184                 ftor(p, port_buffer, port_from_base, base, NULL, runtime);
185 
186 #if 0
187 // practical for debugging if a parameter was changed, but not saved
188             const char* cmp = "/part15/kit0/adpars/GlobalPar/Reson/Prespoints";
189             if(!strncmp(port_buffer, cmp, strlen(cmp)))
190             {
191                 puts("break here");
192             }
193 #endif
194             canonicalize_arg_vals(arg_vals_default, nargs_default,
195                                   strchr(p->name, ':'), meta);
196 
197             auto write_msg = [&res, &meta, &port_buffer]
198                                  (const rtosc_arg_val_t* arg_vals_default,
199                                   rtosc_arg_val_t* arg_vals_runtime,
200                                   int nargs_default, size_t nargs_runtime)
201             {
202                 if(!rtosc_arg_vals_eq(arg_vals_default, arg_vals_runtime,
203                                       nargs_default, nargs_runtime, nullptr))
204                 {
205                     char cur_value_pretty[buffersize] = " ";
206 
207                     map_arg_vals(arg_vals_runtime, nargs_runtime, meta);
208 
209                     rtosc_print_arg_vals(arg_vals_runtime, nargs_runtime,
210                                          cur_value_pretty + 1, buffersize - 1,
211                                          NULL, strlen(port_buffer) + 1);
212                     *res += port_buffer;
213                     *res += cur_value_pretty;
214                     *res += "\n";
215                 }
216             }; // functor write_msg
217 
218             if(arg_vals_runtime[0].type == 'a' && strchr(port_from_base, '/'))
219             {
220                 // These are grouped as an array, but the port structure
221                 // implicits that they shall be handled as single values
222                 // inside their subtrees
223                 //  => We don't print this as an array
224                 //  => All arrays in savefiles have their numbers after
225                 //     the last port separator ('/')
226 
227                 // used if the value of lhs or rhs is range-computed:
228                 rtosc_arg_val_t rlhs, rrhs;
229 
230                 rtosc_arg_val_itr litr, ritr;
231                 rtosc_arg_val_itr_init(&litr, arg_vals_default+1);
232                 rtosc_arg_val_itr_init(&ritr, arg_vals_runtime+1);
233 
234                 auto write_msg_adaptor = [&litr, &ritr,&rlhs,&rrhs,&write_msg](
235                     const Port* p,
236                     const char* port_buffer, const char* old_end,
237                     const Ports&, void*, void*)
238                 {
239                     const rtosc_arg_val_t
240                         * lcur = rtosc_arg_val_itr_get(&litr, &rlhs),
241                         * rcur = rtosc_arg_val_itr_get(&ritr, &rrhs);
242 
243                     if(!rtosc_arg_vals_eq_single(
244                             rtosc_arg_val_itr_get(&litr, &rlhs),
245                             rtosc_arg_val_itr_get(&ritr, &rrhs), nullptr))
246                     {
247                         auto get_sz = [](const rtosc_arg_val_t* a) {
248                             return a->type == 'a' ? (a->val.a.len + 1) : 1; };
249                         // the const-ness does not matter
250                         write_msg(lcur,
251                             const_cast<rtosc_arg_val_t*>(rcur),
252                             get_sz(lcur), get_sz(rcur));
253                     }
254 
255                     rtosc_arg_val_itr_next(&litr);
256                     rtosc_arg_val_itr_next(&ritr);
257                 };
258 
259                 char* old_end_noconst = const_cast<char*>(port_from_base);
260 
261                 // iterate over the whole array
262                 bundle_foreach(*p, p->name, old_end_noconst, port_buffer,
263                                base, NULL, NULL,
264                                write_msg_adaptor, true);
265 
266                 // glue the old end behind old_end_noconst again
267                 refix_old_end(p, old_end_noconst);
268 
269             }
270             else
271             {
272                 write_msg(arg_vals_default, arg_vals_runtime,
273                           nargs_default, nargs_runtime);
274             }
275         }
276     };
277 
278     walk_ports(&ports, port_buffer, buffersize, &data, on_reach_port, false,
279                runtime);
280 
281     if(data.res.length()) // remove trailing newline
282         data.res.resize(data.res.length()-1);
283     return data.res;
284 }
285 
do_dispatch(const char * msg)286 bool savefile_dispatcher_t::do_dispatch(const char* msg)
287 {
288     *loc = 0;
289     RtData d;
290     d.obj = runtime;
291     d.loc = loc; // we're always dispatching at the base
292     d.loc_size = 1024;
293     ports->dispatch(msg, d, true);
294     return !!d.matches;
295 }
296 
default_response(size_t nargs,bool first_round,savefile_dispatcher_t::dependency_t dependency)297 int savefile_dispatcher_t::default_response(size_t nargs,
298                                             bool first_round,
299                                             savefile_dispatcher_t::dependency_t
300                                                 dependency)
301 {
302     // default implementation:
303     // no dependencies  => round 0,
304     // has dependencies => round 1,
305     // not specified    => both rounds
306     return (dependency == not_specified
307             || !(dependency ^ first_round))
308            ? nargs // argument number is not changed
309            : (int)discard;
310 }
311 
on_dispatch(size_t,char *,size_t,size_t nargs,rtosc_arg_val_t *,bool round2,dependency_t dependency)312 int savefile_dispatcher_t::on_dispatch(size_t, char *,
313                                        size_t, size_t nargs,
314                                        rtosc_arg_val_t *,
315                                        bool round2,
316                                        dependency_t dependency)
317 {
318     return default_response(nargs, round2, dependency);
319 }
320 
dispatch_printed_messages(const char * messages,const Ports & ports,void * runtime,savefile_dispatcher_t * dispatcher)321 int dispatch_printed_messages(const char* messages,
322                               const Ports& ports, void* runtime,
323                               savefile_dispatcher_t* dispatcher)
324 {
325     constexpr std::size_t buffersize = 8192;
326     char portname[buffersize], message[buffersize], strbuf[buffersize];
327     int rd, rd_total = 0;
328     int nargs;
329     int msgs_read = 0;
330     bool ok = true;
331 
332     savefile_dispatcher_t dummy_dispatcher;
333     if(!dispatcher)
334         dispatcher = &dummy_dispatcher;
335     dispatcher->ports = &ports;
336     dispatcher->runtime = runtime;
337 
338     // scan all messages twice:
339     //  * in the second round, only dispatch those with ports that depend on
340     //    other ports
341     //  * in the first round, only dispatch all others
342     for(int round = 0; round < 2 && ok; ++round)
343     {
344         msgs_read = 0;
345         rd_total = 0;
346         const char* msg_ptr = messages;
347         while(*msg_ptr && ok)
348         {
349             nargs = rtosc_count_printed_arg_vals_of_msg(msg_ptr);
350             if(nargs >= 0)
351             {
352                 // nargs << 1 is usually too much, but it allows the user to use
353                 // these values (using on_dispatch())
354                 size_t maxargs = std::max(nargs << 1, 16);
355                 STACKALLOC(rtosc_arg_val_t, arg_vals, maxargs);
356                 rd = rtosc_scan_message(msg_ptr, portname, buffersize,
357                                         arg_vals, nargs, strbuf, buffersize);
358                 rd_total += rd;
359 
360                 const Port* port = ports.apropos(portname);
361                 savefile_dispatcher_t::dependency_t dependency =
362                     (savefile_dispatcher_t::dependency_t)
363                     (port
364                     ? !!port->meta()["default depends"]
365                     : (int)savefile_dispatcher_t::not_specified);
366 
367                 // let the user modify the message and the args
368                 // the argument number may have changed, or the user
369                 // wants to discard the message or abort the savefile loading
370                 nargs = dispatcher->on_dispatch(buffersize, portname,
371                                                 maxargs, nargs, arg_vals,
372                                                 round, dependency);
373 
374                 if(nargs == savefile_dispatcher_t::abort)
375                     ok = false;
376                 else
377                 {
378                     if(nargs != savefile_dispatcher_t::discard)
379                     {
380                         const rtosc_arg_val_t* arg_val_ptr;
381                         bool is_array;
382                         if(nargs && arg_vals[0].type == 'a')
383                         {
384                             is_array = true;
385                             // arrays of arrays are not yet supported -
386                             // neither by rtosc_*message, nor by the inner for
387                             // loop below.
388                             // arrays will probably have an 'a' (or #)
389                             assert(arg_vals[0].val.a.type != 'a' &&
390                                    arg_vals[0].val.a.type != '#');
391                             // we won't read the array arg val anymore
392                             --nargs;
393                             arg_val_ptr = arg_vals + 1;
394                         }
395                         else {
396                             is_array = false;
397                             arg_val_ptr = arg_vals;
398                         }
399 
400                         char* portname_end = portname + strlen(portname);
401 
402                         rtosc_arg_val_itr itr;
403                         rtosc_arg_val_t buffer;
404                         const rtosc_arg_val_t* cur;
405 
406                         rtosc_arg_val_itr_init(&itr, arg_val_ptr);
407 
408                         // for bundles, send each element separately
409                         // for non-bundles, send all elements at once
410                         for(size_t arr_idx = 0;
411                             itr.i < (size_t)std::max(nargs,1) && ok; ++arr_idx)
412                         {
413                             // this will fail for arrays of arrays,
414                             // since it only copies one arg val
415                             // (arrays are not yet specified)
416                             size_t i;
417                             const size_t last_pos = itr.i;
418                             const size_t elem_limit = is_array
419                                   ? 1 : std::numeric_limits<int>::max();
420 
421                             // equivalent to the for loop below, in order to
422                             // find out the array size
423                             size_t val_max = 0;
424                             {
425                                 rtosc_arg_val_itr itr2 = itr;
426                                 for(val_max = 0;
427                                     itr2.i - last_pos < (size_t)nargs &&
428                                         val_max < elem_limit;
429                                     ++val_max)
430                                 {
431                                     rtosc_arg_val_itr_next(&itr2);
432                                 }
433                             }
434                             STACKALLOC(rtosc_arg_t, vals, val_max);
435                             STACKALLOC(char, argstr, val_max+1);
436 
437                             for(i = 0;
438                                 itr.i - last_pos < (size_t)nargs &&
439                                     i < elem_limit;
440                                 ++i)
441                             {
442                                 cur = rtosc_arg_val_itr_get(&itr, &buffer);
443                                 vals[i] = cur->val;
444                                 argstr[i] = cur->type;
445                                 rtosc_arg_val_itr_next(&itr);
446                             }
447 
448                             argstr[i] = 0;
449 
450                             if(is_array)
451                                 snprintf(portname_end, 8, "%d", (int)arr_idx);
452 
453                             rtosc_amessage(message, buffersize, portname,
454                                            argstr, vals);
455 
456                             ok = (*dispatcher)(message);
457                         }
458                     }
459                 }
460 
461                 msg_ptr += rd;
462                 ++msgs_read;
463             }
464             else if(nargs == std::numeric_limits<int>::min())
465             {
466                 // this means the (rest of the) file is whitespace only
467                 // => don't increase msgs_read
468                 while(*++msg_ptr) ;
469             }
470             else {
471                 ok = false;
472             }
473         }
474     }
475     return ok ? msgs_read : -rd_total-1;
476 }
477 
save_to_file(const Ports & ports,void * runtime,const char * appname,rtosc_version appver,std::string file_str)478 std::string save_to_file(const Ports &ports, void *runtime,
479                          const char *appname, rtosc_version appver,
480                          std::string file_str)
481 {
482     char rtosc_vbuf[12], app_vbuf[12];
483 
484     if(file_str.empty())
485     {
486         {
487             rtosc_version rtoscver = rtosc_current_version();
488             rtosc_version_print_to_12byte_str(&rtoscver, rtosc_vbuf);
489             rtosc_version_print_to_12byte_str(&appver, app_vbuf);
490         }
491 
492         file_str += "% RT OSC v"; file_str += rtosc_vbuf; file_str += " savefile\n"
493                "% "; file_str += appname; file_str += " v"; file_str += app_vbuf; file_str += "\n";
494     }
495     else
496     {
497         // append mode - no header
498     }
499     file_str += get_changed_values(ports, runtime);
500 
501     return file_str;
502 }
503 
load_from_file(const char * file_content,const Ports & ports,void * runtime,const char * appname,rtosc_version appver,savefile_dispatcher_t * dispatcher)504 int load_from_file(const char* file_content,
505                    const Ports& ports, void* runtime,
506                    const char* appname,
507                    rtosc_version appver,
508                    savefile_dispatcher_t* dispatcher)
509 {
510     char appbuf[128];
511     int bytes_read = 0;
512 
513     if(dispatcher)
514     {
515         dispatcher->app_curver = appver;
516         dispatcher->rtosc_curver = rtosc_current_version();
517     }
518 
519     unsigned vma, vmi, vre;
520     int n = 0;
521 
522     sscanf(file_content,
523            "%% RT OSC v%u.%u.%u savefile%n ", &vma, &vmi, &vre, &n);
524     if(n <= 0 || vma > 255 || vmi > 255 || vre > 255)
525         return -bytes_read-1;
526     if(dispatcher)
527     {
528         dispatcher->rtosc_filever.major = vma;
529         dispatcher->rtosc_filever.minor = vmi;
530         dispatcher->rtosc_filever.revision = vre;
531     }
532     file_content += n;
533     bytes_read += n;
534     n = 0;
535 
536     sscanf(file_content,
537            "%% %128s v%u.%u.%u%n ", appbuf, &vma, &vmi, &vre, &n);
538     if(n <= 0 || strcmp(appbuf, appname) || vma > 255 || vmi > 255 || vre > 255)
539         return -bytes_read-1;
540 
541     if(dispatcher)
542     {
543         dispatcher->app_filever.major = vma;
544         dispatcher->app_filever.minor = vmi;
545         dispatcher->app_filever.revision = vre;
546     }
547     file_content += n;
548     bytes_read += n;
549     n = 0;
550 
551     int rval = dispatch_printed_messages(file_content,
552                                          ports, runtime, dispatcher);
553     return (rval < 0) ? (rval-bytes_read) : rval;
554 }
555 
556 }
557 
558