aade418e201162ecb196467f528ee1dd412666e7
[src/app-framework-binder.git] / src / session.c
1 /*
2  * Copyright (C) 2015 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "local-def.h"
21 #include <dirent.h>
22 #include <string.h>
23 #include <time.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26
27 #define AFB_SESSION_JTYPE "AFB_session"
28 #define AFB_SESSION_JLIST "AFB_sessions"
29 #define AFB_SESSION_JINFO "AFB_infos"
30
31 #define AFB_CURRENT_SESSION "active-session"  // file link name within sndcard dir
32 #define AFB_DEFAULT_SESSION "current-session" // should be in sync with UI
33
34
35
36
37 // verify we can read/write in session dir
38 PUBLIC AFB_error sessionCheckdir (AFB_session *session) {
39
40    int err;
41
42    // in case session dir would not exist create one
43    if (verbose) fprintf (stderr, "AFB:notice checking session dir [%s]\n", session->config->sessiondir);
44    mkdir(session->config->sessiondir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
45
46    // change for session directory
47    err = chdir(session->config->sessiondir);
48    if (err) {
49      fprintf(stderr,"AFB: Fail to chdir to %s error=%s\n", session->config->sessiondir, strerror(err));
50      return err;
51    }
52
53    // verify we can write session in directory
54    json_object *dummy= json_object_new_object();
55    json_object_object_add (dummy, "checked"  , json_object_new_int (getppid()));
56    err = json_object_to_file ("./AFB-probe.json", dummy);
57    if (err < 0) return err;
58
59    return AFB_SUCCESS;
60 }
61
62 // let's return only sessions files
63 STATIC int fileSelect (const struct dirent *entry) {
64    return (strstr (entry->d_name, ".afb") != NULL);
65 }
66
67 STATIC  json_object *checkCardDirExit (AFB_session *session, AFB_request *request ) {
68     int  sessionDir, cardDir;
69
70     // card name should be more than 3 character long !!!!
71     if (strlen (request->plugin) < 3) {
72        return (jsonNewMessage (AFB_FAIL,"Fail invalid plugin=%s", request->plugin));
73     }
74
75     // open session directory
76     sessionDir = open (session->config->sessiondir, O_DIRECTORY);
77     if (sessionDir < 0) {
78           return (jsonNewMessage (AFB_FAIL,"Fail to open directory [%s] error=%s", session->config->sessiondir, strerror(sessionDir)));
79     }
80
81    // create session sndcard directory if it does not exit
82     cardDir = openat (sessionDir, request->plugin,  O_DIRECTORY);
83     if (cardDir < 0) {
84           cardDir  = mkdirat (sessionDir, request->plugin, O_RDWR | S_IRWXU | S_IRGRP);
85           if (cardDir < 0) {
86               return (jsonNewMessage (AFB_FAIL,"Fail to create directory [%s/%s] error=%s", session->config->sessiondir, request->plugin, strerror(cardDir)));
87           }
88     }
89     close (sessionDir);
90     return NULL;
91 }
92
93 // create a session in current directory
94 PUBLIC json_object *sessionList (AFB_session *session, AFB_request *request) {
95     json_object *sessionsJ, *ajgResponse;
96     struct stat fstat;
97     struct dirent **namelist;
98     int  count, sessionDir;
99
100     // if directory for card's sessions does not exist create it
101     ajgResponse = checkCardDirExit (session, request);
102     if (ajgResponse != NULL) return ajgResponse;
103
104     // open session directory
105     sessionDir = open (session->config->sessiondir, O_DIRECTORY);
106     if (sessionDir < 0) {
107           return (jsonNewMessage (AFB_FAIL,"Fail to open directory [%s] error=%s", session->config->sessiondir, strerror(sessionDir)));
108     }
109
110     count = scandirat (sessionDir, request->plugin, &namelist, fileSelect, alphasort);
111     close (sessionDir);
112
113     if (count < 0) {
114         return (jsonNewMessage (AFB_FAIL,"Fail to scan sessions directory [%s/%s] error=%s", session->config->sessiondir, request->plugin, strerror(sessionDir)));
115     }
116     if (count == 0) return (jsonNewMessage (AFB_EMPTY,"[%s] no session at [%s]", request->plugin, session->config->sessiondir));
117
118     // loop on each session file, retrieve its date and push it into json response object
119     sessionsJ = json_object_new_array();
120     while (count--) {
121          json_object *sessioninfo;
122          char timestamp [64];
123          char *filename;
124
125          // extract file name and last modification date
126          filename = namelist[count]->d_name;
127          printf("%s\n", filename);
128          stat(filename,&fstat);
129          strftime (timestamp, sizeof(timestamp), "%c", localtime (&fstat.st_mtime));
130          filename[strlen(filename)-4] = '\0'; // remove .afb extension from filename
131
132          // create an object by session with last update date
133          sessioninfo = json_object_new_object();
134          json_object_object_add (sessioninfo, "date" , json_object_new_string (timestamp));
135          json_object_object_add (sessioninfo, "session" , json_object_new_string (filename));
136          json_object_array_add (sessionsJ, sessioninfo);
137
138          free(namelist[count]);
139     }
140
141     // free scandir structure
142     free(namelist);
143
144     // everything is OK let's build final response
145     ajgResponse = json_object_new_object();
146     json_object_object_add (ajgResponse, "jtype" , json_object_new_string (AFB_SESSION_JLIST));
147     json_object_object_add (ajgResponse, "status"  , jsonNewStatus(AFB_SUCCESS));
148     json_object_object_add (ajgResponse, "data"    , sessionsJ);
149
150     return (ajgResponse);
151 }
152
153 // Create a link toward last used sessionname within sndcard directory
154 STATIC void makeSessionLink (const char *cardname, const char *sessionname) {
155    char linkname [256], filename [256];
156    int err;
157    // create a link to keep track of last uploaded sessionname for this card
158    strncpy (filename, sessionname, sizeof(filename));
159    strncat (filename, ".afb", sizeof(filename));
160
161    strncpy (linkname, cardname, sizeof(linkname));
162    strncat (linkname, "/", sizeof(filename));
163    strncat (linkname, AFB_CURRENT_SESSION, sizeof(linkname));
164    strncat (linkname, ".afb", sizeof(filename));
165    unlink (linkname); // remove previous link if any
166    err = symlink (filename, linkname);
167    if (err < 0) fprintf (stderr, "Fail to create link %s->%s error=%s\n", linkname, filename, strerror(errno));
168 }
169
170 // Load Json session object from disk
171 PUBLIC json_object *sessionFromDisk (AFB_session *session, AFB_request *request, char *name) {
172     json_object *jsonSession, *jtype, *response;
173     const char *ajglabel;
174     char filename [256];
175     int defsession;
176
177     if (name == NULL) {
178         return  (jsonNewMessage (AFB_FATAL,"session name missing &session=MySessionName"));
179     }
180
181     // check for current session request
182     defsession = (strcmp (name, AFB_DEFAULT_SESSION) ==0);
183
184     // if directory for card's sessions does not exist create it
185     response = checkCardDirExit (session, request);
186     if (response != NULL) return response;
187
188     // add name and file extension to session name
189     strncpy (filename, request->plugin, sizeof(filename));
190     strncat (filename, "/", sizeof(filename));
191     if (defsession) strncat (filename, AFB_CURRENT_SESSION, sizeof(filename)-1);
192     else strncat (filename, name, sizeof(filename)-1);
193     strncat (filename, ".afb", sizeof(filename));
194
195     // just upload json object and return without any further processing
196     jsonSession = json_object_from_file (filename);
197
198     if (jsonSession == NULL)  return (jsonNewMessage (AFB_EMPTY,"File [%s] not found", filename));
199
200     // verify that file is a JSON ALSA session type
201     if (!json_object_object_get_ex (jsonSession, "jtype", &jtype)) {
202         json_object_put   (jsonSession);
203         return  (jsonNewMessage (AFB_EMPTY,"File [%s] 'jtype' descriptor not found", filename));
204     }
205
206     // check type value is AFB_SESSION_JTYPE
207     ajglabel = json_object_get_string (jtype);
208     if (strcmp (AFB_SESSION_JTYPE, ajglabel)) {
209        json_object_put   (jsonSession);
210        return  (jsonNewMessage (AFB_FATAL,"File [%s] jtype=[%s] != [%s]", filename, ajglabel, AFB_SESSION_JTYPE));
211     }
212
213     // create a link to keep track of last uploaded session for this card
214     if (!defsession) makeSessionLink (request->plugin, name);
215
216     return (jsonSession);
217 }
218
219 // push Json session object to disk
220 PUBLIC json_object * sessionToDisk (AFB_session *session, AFB_request *request, char *name, json_object *jsonSession) {
221    char filename [256];
222    time_t rawtime;
223    struct tm * timeinfo;
224    int err, defsession;
225    static json_object *response;
226
227    // we should have a session name
228    if (name == NULL) return (jsonNewMessage (AFB_FATAL,"session name missing &session=MySessionName"));
229
230    // check for current session request
231    defsession = (strcmp (name, AFB_DEFAULT_SESSION) ==0);
232
233    // if directory for card's sessions does not exist create it
234    response = checkCardDirExit (session, request);
235    if (response != NULL) return response;
236
237    // add cardname and file extension to session name
238    strncpy (filename, request->plugin, sizeof(filename));
239    strncat (filename, "/", sizeof(filename));
240    if (defsession) strncat (filename, AFB_CURRENT_SESSION, sizeof(filename)-1);
241    else strncat (filename, name, sizeof(filename)-1);
242    strncat (filename, ".afb", sizeof(filename)-1);
243
244
245    json_object_object_add(jsonSession, "jtype", json_object_new_string (AFB_SESSION_JTYPE));
246
247    // add a timestamp and store session on disk
248    time ( &rawtime );  timeinfo = localtime ( &rawtime );
249    // A copy of the string is made and the memory is managed by the json_object
250    json_object_object_add (jsonSession, "timestamp", json_object_new_string (asctime (timeinfo)));
251
252
253    // do we have extra session info ?
254    if (request->post) {
255        static json_object *info, *jtype;
256        const char  *ajglabel;
257
258        // extract session info from args
259        info = json_tokener_parse (request->post);
260        if (!info) {
261             response = jsonNewMessage (AFB_FATAL,"sndcard=%s session=%s invalid json args=%s", request->plugin, name, request->post);
262             goto OnErrorExit;
263        }
264
265        // info is a valid AFB_info type
266        if (!json_object_object_get_ex (info, "jtype", &jtype)) {
267             response = jsonNewMessage (AFB_EMPTY,"sndcard=%s session=%s No 'AFB_type' args=%s", request->plugin, name, request->post);
268             goto OnErrorExit;
269        }
270
271        // check type value is AFB_INFO_JTYPE
272        ajglabel = json_object_get_string (jtype);
273        if (strcmp (AFB_SESSION_JINFO, ajglabel)) {
274               json_object_put   (info); // release info json object
275               response = jsonNewMessage (AFB_FATAL,"File [%s] jtype=[%s] != [%s] data=%s", filename, ajglabel, AFB_SESSION_JTYPE, request->post);
276               goto OnErrorExit;
277        }
278
279        // this is valid info data for our session
280        json_object_object_add (jsonSession, "info", info);
281    }
282
283    // Finally save session on disk
284    err = json_object_to_file (filename, jsonSession);
285    if (err < 0) {
286         response = jsonNewMessage (AFB_FATAL,"Fail save session = [%s] to disk", filename);
287         goto OnErrorExit;
288    }
289
290
291    // create a link to keep track of last uploaded session for this card
292    if (!defsession) makeSessionLink (request->plugin, name);
293
294    // we're donne let's return status message
295    response = jsonNewMessage (AFB_SUCCESS,"Session= [%s] saved on disk", filename);
296    json_object_put (jsonSession);
297    return (response);
298
299 OnErrorExit:
300    json_object_put (jsonSession);
301    return response;
302 }