git.jasLogic.tech
- better error handling and minor changes in photobox.c
[photobox.git] / src / photobox.c
1 // Copyright (C) 2019 Jaslo Ziska
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <time.h>
6
7 #include <gtk/gtk.h>
8 #include <glib.h>
9
10 #include <mipea/gpio.h>
11 #include <curl/curl.h>
12 #include <qrencode.h>
13
14 #include "photobox_photo.h"
15 #include "apikey.h" // are you looking for this? :)
16
17 // missing in glib???
18 #define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void)) (f))
19
20 void pb_exit(void);
21
22 void pb_show_main(void);
23 void pb_show_send(void);
24 void pb_show_qr(void);
25
26 gboolean pb_poll_buttton(void);
27 gboolean pb_countdown(void);
28 void pb_takepic(void);
29
30 int pb_cp_dp(char *id);
31
32 static GtkWidget *img;
33 static GtkWidget *button_take;
34 static GtkWidget *label_countdown;
35
36 static GtkWidget *button_upload;
37 static GtkWidget *button_cancel;
38
39 static GtkWidget *qr_img;
40 static GtkWidget *qr_label;
41 static GtkWidget *qr_button_back;
42
43 static const unsigned int IMG_WIDTH = 512;
44 static const unsigned int IMG_HEIGHT = 384;
45 static const unsigned int QR_SIZE = 350;
46
47 static unsigned int button_pin = 26;
48
49 int main(int argc, char *argv[])
50 {
51 if (pb_ph_init() != 0) {
52 fprintf(stderr, "ERROR in pb_ph_init\n");
53 return EXIT_FAILURE;
54 }
55 if (gpio_map() == NULL) {
56 fprintf(stderr, "ERROR in gpio_map\n");
57 return EXIT_FAILURE;
58 }
59 gtk_init(&argc, &argv);
60
61 signal(SIGCHLD, SIG_IGN);
62
63 // GPIO
64 gpio_inp(button_pin);
65 gpio_pud(button_pin, PUD_UP);
66
67 // WINDOW
68 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
69 gtk_window_set_title(GTK_WINDOW(window), "Photobox");
70 gtk_window_fullscreen(GTK_WINDOW(window));
71 //g_signal_connect(window, "delete-event", G_CALLBACK(pb_gui_on_delete_main), NULL);
72 g_signal_connect(window, "destroy", G_CALLBACK(pb_exit), NULL);
73 // hide cursor
74 gtk_widget_realize(window);
75 GdkCursor* cursor_blank = gdk_cursor_new(GDK_BLANK_CURSOR);
76 gdk_window_set_cursor(gtk_widget_get_window(window), cursor_blank);
77
78 // BOX VERTICAL
79 GtkWidget *box_v = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
80 gtk_container_add(GTK_CONTAINER(window), box_v);
81 gtk_widget_show(box_v);
82
83 // BOX HORIZONTAL
84 GtkWidget *box_h = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
85 gtk_box_pack_end(GTK_BOX(box_v), box_h, TRUE, FALSE, 0);
86 gtk_widget_show(box_h);
87
88 // IMAGE
89 img = gtk_image_new_from_file("no_signal.png");
90 gtk_box_pack_end(GTK_BOX(box_v), img, TRUE, FALSE, 0);
91
92 // BUTTON: TAKE PICTURE
93 button_take = gtk_button_new();
94 // label
95 GtkWidget *button_take_label = gtk_label_new(NULL);
96 gtk_label_set_markup(GTK_LABEL(button_take_label), "<span font='50'>Bild aufnehmen</span>");
97 gtk_container_add(GTK_CONTAINER(button_take), button_take_label);
98 gtk_widget_show(button_take_label);
99 // /label
100 gtk_box_pack_start(GTK_BOX(box_h), button_take, TRUE, FALSE, 0);
101 g_signal_connect(button_take, "clicked", G_CALLBACK(pb_countdown), NULL);
102
103 // COUNTDOWN LABEL
104 label_countdown = gtk_label_new(NULL);
105 gtk_box_pack_end(GTK_BOX(box_v), label_countdown, TRUE, FALSE, 0);
106
107 // BUTTON: UPLOAD
108 button_upload = gtk_button_new_with_label("Hochladen");
109 //g_signal_connect(button_upload, "clicked", G_CALLBACK(pb_exit), NULL);
110 g_signal_connect(button_upload, "clicked", G_CALLBACK(pb_show_qr), NULL);
111 gtk_box_pack_start(GTK_BOX(box_h), button_upload, TRUE, FALSE, 0);
112
113 // BUTTON: CANCEL
114 button_cancel = gtk_button_new_with_label("Abbrechen");
115 //g_signal_connect(button_cancel, "clicked", G_CALLBACK(pb_exit), NULL);
116 g_signal_connect(button_cancel, "clicked", G_CALLBACK(pb_show_main), NULL);
117 gtk_box_pack_start(GTK_BOX(box_h), button_cancel, TRUE, FALSE, 0);
118
119 // QR CODE IMAGE
120 qr_img = gtk_image_new_from_file("no_signal.png");
121 gtk_box_pack_end(GTK_BOX(box_v), qr_img, TRUE, FALSE, 0);
122
123 // QR CODE LABEL
124 qr_label = gtk_label_new(NULL);
125 gtk_label_set_justify(GTK_LABEL(qr_label), GTK_JUSTIFY_CENTER);
126 gtk_label_set_line_wrap(GTK_LABEL(qr_label), TRUE);
127 gtk_box_pack_end(GTK_BOX(box_v), qr_label, TRUE, FALSE, 0);
128
129 // QR BUTTON BACK
130 qr_button_back = gtk_button_new_with_label("Zurück");
131 g_signal_connect(qr_button_back, "clicked", G_CALLBACK(pb_show_main), NULL);
132 //g_signal_connect(qr_button_back, "clicked", G_CALLBACK(pb_exit), NULL);
133 gtk_box_pack_start(GTK_BOX(box_h), qr_button_back, TRUE, FALSE, 0);
134
135 pb_show_main();
136 gtk_widget_show(window);
137
138 gtk_main();
139
140 return EXIT_SUCCESS;
141 }
142
143 void pb_exit(void)
144 {
145 gpio_unmap();
146 pb_ph_uninit();
147 gtk_main_quit();
148 }
149
150 void pb_show_main(void)
151 {
152 gtk_widget_hide(img);
153 gtk_widget_hide(button_upload);
154 gtk_widget_hide(button_cancel);
155 gtk_widget_hide(qr_img);
156 gtk_widget_hide(qr_label);
157 gtk_widget_hide(qr_button_back);
158
159 gtk_widget_show(button_take);
160
161 // take picture with button (ugly)
162 g_idle_add(G_SOURCE_FUNC(pb_poll_buttton), NULL);
163 }
164
165 void pb_show_send(void)
166 {
167 gtk_widget_show(img);
168 gtk_widget_show(button_upload);
169 gtk_widget_show(button_cancel);
170
171 gtk_widget_hide(label_countdown);
172 }
173
174 void pb_show_qr(void)
175 {
176 gtk_widget_hide(img);
177 gtk_widget_hide(button_upload);
178 gtk_widget_hide(button_cancel);
179
180 gtk_widget_show(qr_img);
181 gtk_widget_show(qr_label);
182 gtk_widget_show(qr_button_back);
183
184 // id from time
185 char id[7];
186 strftime(id, 7, "%H%M%S", localtime(&(time_t) {time(NULL)}));
187
188 pid_t pid = fork();
189 if (pid == 0) {
190 // child -> copy and upload
191 if (pb_cp_dp(id) != 0) {
192 fprintf(stderr, "ERROR in pb_cp_dp\n");
193 exit(EXIT_FAILURE);
194 } else {
195 exit(EXIT_SUCCESS);
196 }
197 } else if (pid < 0) {
198 fprintf(stderr, "ERROR in fork (big problem)\n");
199
200 // try to at least save the picture (the ugly way)
201 char *cmd = "cp tmp.jpg bilder/xxxxxx.jpg";
202 for (unsigned int i = 0; i < 6; ++i) {
203 cmd[i + 18] = id[i];
204 }
205 if (system(cmd) != 0)
206 perror("ERROR in system(), picture could not be saved");
207
208 // go back to start, dont show qr
209 pb_show_main();
210 return;
211 }
212
213 // parent -> continue GUI
214
215 // url qr should point to
216 // example: https://www.dropbox.com/sh/3pq0pwednyuig86/AACx0_vjn-liY5_pP_C3nJD8a?dl=0&preview=c1b052c5.jpg
217 char url[] = "https://www.dropbox.com/sh/3pq0pwednyuig86/AACx0_vjn-liY5_pP_C3nJD8a?dl=0&preview=xxxxxx.jpg";
218 for (unsigned int i = 0; i < 6; ++i) {
219 url[i + 82] = id[i];
220 }
221
222 // generate qr code
223 QRcode *qr = QRcode_encodeString(url, 0, QR_ECLEVEL_H, QR_MODE_8, 1);
224 if (qr == NULL) {
225 perror("Failed to generate QR-Code");
226 pb_show_main();
227 return;
228 }
229
230 unsigned int size = qr->width;
231 char values[10];
232 sprintf(values, "%d %d 2 1", size, size);
233
234 const char *xface[3 + size];
235 xface[0] = values;
236 xface[1] = "a c #ffffff";
237 xface[2] = "b c #000000";
238
239 char qrcode_char[size][size + 1];
240 for (unsigned int i = 0; i < size; ++i) {
241 qrcode_char[i][size] = '\0';
242 for (unsigned int j = 0; j < size; ++j) {
243 qrcode_char[i][j] = qr->data[j + i * size] & 1 ? 'b' : 'a';
244 }
245 xface[3 + i] = &qrcode_char[i][0];
246 }
247 QRcode_free(qr);
248
249 GdkPixbuf *pb = gdk_pixbuf_new_from_xpm_data(xface);
250
251 pb = gdk_pixbuf_scale_simple(pb, QR_SIZE, QR_SIZE, GDK_INTERP_NEAREST);
252 gtk_image_set_from_pixbuf((GtkImage *)qr_img, pb);
253 g_clear_object(&pb);
254
255 char text[] = "<span font='12'>QR-Code scannen oder auf <b>https://www.dropbox.com/sh/3pq0pwednyuig86/AACx0_vjn-liY5_pP_C3nJD8a</b> gehen. <small>Der Upload kann einige Minuten dauern.</small></span>";
256 gtk_label_set_markup(GTK_LABEL(qr_label), text);
257 }
258
259 // not proud of this
260 gboolean pb_poll_buttton(void)
261 {
262 if (gpio_tst(button_pin) == 0) {
263 pb_countdown();
264 return G_SOURCE_REMOVE;
265 } else {
266 return G_SOURCE_CONTINUE;
267 }
268 }
269
270 // neither of this
271 gboolean pb_countdown(void)
272 {
273 static int num = 0;
274
275 switch(num) {
276 case 0:
277 num = 5;
278
279 g_idle_remove_by_data(NULL);
280
281 gtk_label_set_markup(GTK_LABEL(label_countdown),
282 "<span font='300' color='red'>5</span>");
283 gtk_widget_hide(button_take);
284 gtk_widget_show(label_countdown);
285
286 g_timeout_add(1000, G_SOURCE_FUNC(pb_countdown), NULL);
287 return G_SOURCE_CONTINUE;
288 case 5:
289 num = 4;
290 gtk_label_set_markup(GTK_LABEL(label_countdown),
291 "<span font='300' color='red'>4</span>");
292 return G_SOURCE_CONTINUE;
293 case 4:
294 num = 3;
295 gtk_label_set_markup(GTK_LABEL(label_countdown),
296 "<span font='300' color='red'>3</span>");
297 return G_SOURCE_CONTINUE;
298 case 3:
299 num = 2;
300 gtk_label_set_markup(GTK_LABEL(label_countdown),
301 "<span font='300' color='red'>2</span>");
302 return G_SOURCE_CONTINUE;
303 case 2:
304 num = 1;
305 gtk_label_set_markup(GTK_LABEL(label_countdown),
306 "<span font='300' color='red'>1</span>");
307 return G_SOURCE_CONTINUE;
308 case 1:
309 num = 0;
310 gtk_label_set_markup(GTK_LABEL(label_countdown),
311 "<span font='300' color='red'>0</span>");
312
313 // do all pendfing events (show 0) before taking picture
314 while (gtk_events_pending()) {
315 gtk_main_iteration();
316 }
317
318 // take pic
319 pb_takepic();
320 pb_show_send();
321 return G_SOURCE_REMOVE;
322 }
323
324 printf("ERROR: this should never happen?\n");
325 return G_SOURCE_REMOVE;
326 }
327
328 void pb_takepic(void)
329 {
330 char *fn = "tmp.jpg";
331 if (pb_ph_capture_file(fn) != 0) {
332 fprintf(stderr, "ERROR in pb_ph_capture_file\n");
333 gtk_main_quit();
334 return;
335 }
336
337 GdkPixbuf *pb = gdk_pixbuf_new_from_file(fn, NULL);
338 pb = gdk_pixbuf_scale_simple(pb, IMG_WIDTH, IMG_HEIGHT, GDK_INTERP_BILINEAR);
339 gtk_image_set_from_pixbuf(GTK_IMAGE(img), pb);
340 g_clear_object(&pb);
341 }
342
343 int pb_cp_dp(char *id)
344 {
345 char buffer[4096];
346 size_t num_read;
347 size_t dest_fsize;
348
349 char src[] = "tmp.jpg";
350 char dest[] = "bilder/xxxxxx.jpg";
351 char dropbox_arg[] = "Dropbox-API-Arg: {\"path\": \"/bilder/xxxxxx.jpg\",\"mode\": \"add\",\"autorename\": true,\"mute\": false,\"strict_conflict\": false}";
352
353 for (unsigned int i = 0; i < 6; ++i) {
354 dest[i + 7] = id[i];
355 dropbox_arg[i + 35] = id[i];
356 }
357
358 FILE *src_file = fopen(src, "rb");
359 if (src_file == NULL) {
360 perror("Error opening tmp.jpg");
361 return -1;
362 }
363 FILE *dest_file = fopen(dest, "wb+");
364 if (dest_file == NULL) {
365 fclose(src_file);
366 perror("Error opening destination file");
367 return -1;
368 }
369
370 // copy file
371 do {
372 num_read = fread(buffer, 1, 4096, src_file);
373 dest_fsize += num_read;
374 if (fwrite(buffer, 1, num_read, dest_file) != num_read && ferror(dest_file) != 0) {
375 fprintf(stderr, "Error writing to destination file");
376 fclose(src_file);
377 goto error_file;
378 }
379 } while(num_read == 4096);
380
381 if (ferror(src_file) != 0) {
382 fclose(src_file);
383 fprintf(stderr, "Error reading from tmp.jpg\n");
384 perror("^");
385 goto error_file;
386 }
387
388 fclose(src_file);
389
390 rewind(dest_file); // from beginning for upload
391
392 // upload
393 curl_global_init(CURL_GLOBAL_ALL);
394 CURL *curl = curl_easy_init();
395 if (curl == NULL) {
396 fprintf(stderr, "ERROR in curl_easy_init\n");
397 curl_global_cleanup();
398 goto error_file;
399 }
400
401 struct curl_slist *header = NULL;
402 header = curl_slist_append(header, "Authorization: Bearer " APIKEY);
403 header = curl_slist_append(header, dropbox_arg);
404 header = curl_slist_append(header, "Content-Type: application/octet-stream");
405 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
406
407 curl_easy_setopt(curl, CURLOPT_URL, "https://content.dropboxapi.com/2/files/upload");
408
409 curl_easy_setopt(curl, CURLOPT_POST, 1L); // post request
410 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, dest_fsize); // file size
411 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); // use read callback
412 curl_easy_setopt(curl, CURLOPT_READDATA, dest_file); // file pointer
413 curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL); // default callback
414
415 curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); // disable output
416
417 CURLcode ret = curl_easy_perform(curl);
418 if (ret != CURLE_OK) {
419 fprintf(stderr, "ERROR in curl_easy_perform: %s\n", curl_easy_strerror(ret));
420 goto error_curl;
421 }
422
423 error_curl:
424 curl_slist_free_all(header);
425 curl_easy_cleanup(curl);
426
427 curl_global_cleanup();
428
429 error_file:
430 fclose(dest_file);
431
432 return ret;
433 }