fce8426c9e46c3caa9154d9787de66871dcad89b
[melted_gui] / src / library.c
1 /*
2 * library.c -- GTK+ 2 melted gui
3 * Copyright (C) 2012 Maksym Veremeyenko <verem@m1stereo.tv>
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 2 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, write to the Free Software Foundation,
17 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #ifndef _GNU_SOURCE
25 #define _GNU_SOURCE
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <pthread.h>
34 #include <string.h>
35
36 #include <mvcp/mvcp.h>
37 #include <mvcp/mvcp_remote.h>
38
39 #include "instance.h"
40 #include "ui.h"
41 #include "timecode.h"
42 #include "support.h"
43
44 void library_release(instance_t* app)
45 {
46 mvcp_close(app->library.handle[0]);
47 mvcp_parser_close(app->library.handle[1]);
48 };
49
50 static void library_add_fake(instance_t* app, GtkTreeStore *tree_store, GtkTreeIter* parent)
51 {
52 GtkTreeIter iter;
53 gtk_tree_store_append(tree_store, &iter, parent);
54 gtk_tree_store_set(tree_store, &iter, -1);
55 };
56
57 static int library_init_load(instance_t* app)
58 {
59 GtkTreeIter iter;
60 GtkTreeStore *tree_store;
61
62 tree_store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(app->library_tree)));
63 gtk_tree_store_clear(tree_store);
64
65 gtk_tree_store_append(tree_store, &iter, NULL);
66 gtk_tree_store_set(tree_store, &iter,
67 0, app->library.icons[0],
68 1, "<dir>>",
69 2, "LIBRARY",
70 3, NULL,
71 4, NULL,
72 5, FALSE,
73 6, "red",
74 -1 );
75 library_add_fake(app, tree_store, &iter);
76
77 gtk_tree_view_collapse_all(GTK_TREE_VIEW(app->library_tree));
78
79 return 0;
80 };
81
82 static void library_add_item(instance_t* app, GtkTreeStore *treestore, GtkTreeIter *iter, mvcp_dir_entry_t* e)
83 {
84 GtkTreeIter this, child;
85
86 if(e->dir)
87 {
88 gtk_tree_store_prepend(treestore, &this, iter);
89
90 gtk_tree_store_set(treestore, &this,
91 0, app->library.icons[0],
92 1, "<dir>",
93 2, e->name,
94 3, e,
95 4, NULL,
96 5, FALSE,
97 6, "red",
98 -1 );
99
100 gtk_tree_store_append(treestore, &child, &this);
101 gtk_tree_store_set(treestore, &child, -1);
102 }
103 else
104 {
105 gtk_tree_store_append(treestore, &this, iter);
106
107 gtk_tree_store_set(treestore, &this,
108 0, app->library.icons[1],
109 1, "<file>",
110 2, e->name,
111 3, e,
112 4, NULL,
113 5, FALSE,
114 6, "red",
115 -1 );
116 };
117 };
118
119 static void on_library_row_expanded
120 (
121 GtkTreeView *treeview,
122 GtkTreeIter *iter,
123 GtkTreePath *path,
124 gpointer user_data
125 )
126 {
127 int i;
128 char* p;
129 GtkTreeIter fake;
130 GtkTreeModel *model;
131 GdkCursor* cursor;
132 mvcp_dir dir;
133 mvcp_dir_entry_t *e, entry;
134 instance_t* app = (instance_t*)user_data;
135
136 g_warning("on_library_row_expanded: HERE");
137
138 /* Set busy cursor */
139 cursor = gdk_cursor_new(GDK_WATCH);
140 gdk_window_set_cursor(gtk_widget_get_toplevel(GTK_WIDGET(treeview))->window, cursor);
141 gdk_cursor_unref(cursor);
142 gdk_flush();
143
144 model = gtk_tree_view_get_model(treeview);
145
146 /* save fake item */
147 gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &fake, iter);
148
149 /* request mvcp entry */
150 gtk_tree_model_get(GTK_TREE_MODEL(model), iter,
151 3, &e,
152 -1);
153
154 /* setup root path */
155 if(!e)
156 p = "/home/studio/Videos";
157 else
158 p = e->full;
159
160
161 /* read dir */
162 dir = mvcp_dir_init(app->library.handle[0], p);
163 for (i = 0; i < mvcp_dir_count(dir); i++)
164 {
165 if(mvcp_ok != mvcp_dir_get(dir, i, &entry))
166 continue;
167
168 // g_warning("on_library_row_expanded: path=[%s], entry.dur=[%d], entry.full=[%s], entry.name[%s]",
169 // p, entry.dir, entry.full, entry.name);
170
171 e = (mvcp_dir_entry_t*)malloc(sizeof(mvcp_dir_entry_t));
172 memset(e, 0, sizeof(mvcp_dir_entry_t));
173 *e = entry;
174
175 library_add_item(app, GTK_TREE_STORE(model), iter, e);
176 };
177
178 /* restore cursor */
179 gdk_window_set_cursor(gtk_widget_get_toplevel(GTK_WIDGET(treeview))->window, NULL);
180
181 /* delete fake item */
182 gtk_tree_store_remove(GTK_TREE_STORE(model), &fake);
183 };
184
185 static void on_library_row_collapsed
186 (
187 GtkTreeView *treeview,
188 GtkTreeIter *iter,
189 GtkTreePath *path,
190 gpointer user_data
191 )
192 {
193 GtkTreeModel *model;
194 GtkTreeIter child;
195 g_warning("on_library_row_collapsed: HERE");
196
197 /* delete all items */
198 model = gtk_tree_view_get_model(treeview);
199 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &child, iter))
200 {
201 mvcp_dir_entry_t *e;
202
203 /* request mvcp entry */
204 gtk_tree_model_get(GTK_TREE_MODEL(model), &child,
205 3, &e,
206 -1);
207
208 /* free entry */
209 if(e) free(e);
210
211 gtk_tree_store_remove(GTK_TREE_STORE(model), &child);
212 };
213
214 /* add a fake element */
215 library_add_fake(user_data, GTK_TREE_STORE(model), iter);
216 };
217
218 void library_init(instance_t* app)
219 {
220 /* connect to library */
221 app->library.handle[1] = mvcp_parser_init_remote(app->players.host, 5250);
222 app->library.handle[0] = mvcp_init(app->library.handle[1]);
223 if(mvcp_connect(app->library.handle[0]) != mvcp_ok)
224 {
225 g_warning("library_init: failed to connect to server %s", app->players.host);
226 return;
227 };
228
229 /* setup icons */
230 app->library.icons[0] = create_pixbuf("Axialis_Team_playlist_open_16x16.png");
231 app->library.icons[1] = create_pixbuf("Axialis_Team_playlist_save_16x16.png");
232
233 /* load lib */
234 library_init_load(app);
235
236 /* set handlers */
237 gtk_signal_connect(GTK_OBJECT(app->library_tree), "row-expanded",
238 GTK_SIGNAL_FUNC(on_library_row_expanded), app);
239 gtk_signal_connect(GTK_OBJECT(app->library_tree), "row-collapsed",
240 GTK_SIGNAL_FUNC(on_library_row_collapsed), app);
241
242 #if 0
243 pthread_mutex_lock(&app->library.lock);
244
245 if(app->library.filename[0])
246 {
247 app->library.count = MAX_LIBRARY_ITEMS;
248 omnplay_library_load_file(app->library.item, &app->library.count, app->library.filename);
249 };
250
251 omnplay_library_sort(app);
252
253 pthread_mutex_unlock(&app->library.lock);
254
255 omnplay_library_draw(app);
256 #endif
257 };
258
259 #if 0
260 playlist_item_t* omnplay_library_find(omnplay_instance_t* app, char* id)
261 {
262 int i;
263 playlist_item_t* item = NULL;
264
265 pthread_mutex_lock(&app->library.lock);
266
267 for(i = 0; i < app->library.count && !item; i++)
268 if(!strcasecmp(id, app->library.item[i].id))
269 item = &app->library.item[i];
270
271 pthread_mutex_unlock(&app->library.lock);
272
273 return item;
274 };
275
276 int omnplay_library_normalize_item(omnplay_instance_t* app, playlist_item_t* item)
277 {
278 int r = 0;
279 playlist_item_t* lib;
280 playlist_item_t prev;
281
282 pthread_mutex_lock(&app->library.lock);
283
284 prev = *item;
285
286 lib = omnplay_library_find(app, item->id);
287
288 item->error = 0;
289
290 if(lib)
291 {
292 if(!item->title[0])
293 {
294 strcpy(item->title, lib->title);
295 r++;
296 };
297
298 if(item->in < lib->in || item->in >= (lib->in + lib->dur))
299 {
300 item->in = lib->in;
301 r++;
302 };
303
304 if(!item->dur || (item->in + item->dur) > (lib->in + lib->dur))
305 {
306 item->dur = lib->in + lib->dur - item->in;
307 r++;
308 };
309
310 if(r)
311 g_warning("omnplay_library_normalize_item: [%s,%d,%d]->[%s,%d,%d]\n",
312 prev.title, prev.in, prev.dur, item->title, item->in, item->dur);
313 }
314 else
315 {
316 r = 1;
317 item->error = PLAYLIST_ITEM_ERROR_LIB;
318 };
319
320 pthread_mutex_unlock(&app->library.lock);
321
322 return r;
323 };
324
325 int omnplay_library_relink_item(omnplay_instance_t* app, playlist_item_t* item)
326 {
327 int r = 0;
328 playlist_item_t* lib;
329
330 pthread_mutex_lock(&app->library.lock);
331
332 lib = omnplay_library_find(app, item->id);
333
334 item->error = 0;
335
336 if(lib)
337 {
338 r = 1;
339 strcpy(item->title, lib->title);
340 item->dur = lib->dur;
341 item->in = lib->in;
342 }
343 else
344 {
345 r = 1;
346 item->error = PLAYLIST_ITEM_ERROR_LIB;
347 };
348
349 pthread_mutex_unlock(&app->library.lock);
350
351 return r;
352 };
353
354 void omnplay_library_sort(omnplay_instance_t* app)
355 {
356 int i, j, m;
357 playlist_item_t item;
358
359 for(i = 0; i < app->library.count; i++)
360 {
361 /* find max */
362 for(j = i + 1, m = i; j < app->library.count; j++)
363 if(strcasecmp(app->library.item[j].id, app->library.item[m].id) < 0)
364 m = j;
365
366 if(m != i)
367 {
368 item = app->library.item[i];
369 app->library.item[i] = app->library.item[m];
370 app->library.item[m] = item;
371 };
372 };
373 };
374
375 int omnplay_library_load_file(playlist_item_t* items, int *pcount, char* filename)
376 {
377 int i, c = 0, r = 0;
378 FILE* f;
379 char *l;
380 int limit = *pcount;
381 playlist_item_t item;
382
383 /* allocate space for strings and items */
384 l = malloc(PATH_MAX);
385
386 *pcount = 0;
387
388 /* open and process file */
389 if((f = fopen(filename, "rt")))
390 {
391 while(!feof(f) && c < limit)
392 {
393 char *s, *sp_r, *sp_b;
394
395 /* load string */
396 memset(l, 0, PATH_MAX);
397 fgets(l, PATH_MAX, f);
398
399 /* remove newlines */
400 if( (s = strchr(l, '\n')) ) *s = 0;
401 if( (s = strchr(l, '\r')) ) *s = 0;
402
403 /* check for empty line */
404 if(l[0] && l[0] != '#' && l[0] != '|')
405 {
406 memset(&item, 0, sizeof(playlist_item_t));
407
408 for(i = 0, sp_b = l; (NULL != (sp_r = strtok(sp_b, "\t"))); i++, sp_b = NULL)
409 {
410 switch(i)
411 {
412 case 0: strncpy(item.id, sp_r, PATH_MAX); break;
413 case 1: tc2frames(sp_r, 25.0, &item.in); break;
414 case 2: tc2frames(sp_r, 25.0, &item.dur); break;
415 case 3: strncpy(item.title, sp_r, PATH_MAX); break;
416 };
417 };
418
419 /* insert item */
420 items[c++] = item;
421 }
422 else
423 g_warning("omnplay_library_load_file: ignored line [%s]\n", l);
424 }
425
426 fclose(f);
427 }
428 else
429 r = -1;
430
431 /* free data */
432 free(l);
433
434 *pcount = c;
435
436 g_warning("omnplay_library_load_file: loaded [%d] items from [%s] file, limit [%d]\n", c, filename, limit);
437
438 return r;
439 };
440
441
442 static void omnplay_library_save_file(playlist_item_t* item, int count, char* filename)
443 {
444 int i;
445 FILE* f;
446
447 if((f = fopen(filename, "wt")))
448 {
449 char tc_in[32], tc_dur[32];
450
451 for(i = 0; i < count; i++)
452 fprintf(f, "%s\t%s\t%s\t%s\n",
453 item[i].id,
454 frames2tc(item[i].in, 25.0, tc_in),
455 frames2tc(item[i].dur, 25.0, tc_dur),
456 item[i].title);
457 fclose(f);
458 g_warning("omnplay_library_save_file: written [%d] lines to file [%s]\n", count, filename);
459 };
460 };
461
462 void omnplay_library_save(omnplay_instance_t* app)
463 {
464 pthread_mutex_lock(&app->library.lock);
465
466 if(app->library.filename[0])
467 omnplay_library_save_file(app->library.item, app->library.count,
468 app->library.filename);
469
470 pthread_mutex_unlock(&app->library.lock);
471 };
472
473 static void omnplay_get_content_cb(omnplay_instance_t* app, playlist_item_t* item, void* data)
474 {
475 if(!(app->library.id_display_idx % app->library.id_display_rate))
476 omnplay_set_status(app, item->id);
477 app->library.id_display_idx++;
478 };
479
480 static void* omnplay_library_refresh_proc(void* data)
481 {
482 omnplay_instance_t* app = (omnplay_instance_t*)data;
483 int count, i;
484 playlist_item_t* items;
485
486 gdk_threads_enter();
487 gtk_widget_set_sensitive(app->window, FALSE);
488 gdk_flush();
489 gdk_threads_leave();
490
491 omnplay_set_status(app, "Updating library...");
492
493 items = (playlist_item_t*)malloc(sizeof(playlist_item_t) * MAX_LIBRARY_ITEMS);
494
495 count = omnplay_get_content(app, items, MAX_LIBRARY_ITEMS, omnplay_get_content_cb, NULL);
496
497 if(count > 0)
498 {
499 omnplay_set_status(app, "Quering whois...");
500
501 if(app->library.whois[0])
502 omnplay_whois_list(app, items, &count);
503
504 omnplay_set_status(app, "Setting library...");
505
506 pthread_mutex_lock(&app->library.lock);
507
508 for(i = 0; i < count; i++)
509 app->library.item[i] = items[i];
510
511 app->library.count = count;
512
513 omnplay_library_sort(app);
514
515 pthread_mutex_unlock(&app->library.lock);
516
517 gdk_threads_enter();
518 omnplay_library_draw(app);
519 gdk_flush();
520 gdk_threads_leave();
521 };
522
523 omnplay_set_status(app, "Normalizing playlist...");
524
525 free(items);
526
527 gdk_threads_enter();
528 omnplay_playlist_normalize(app);
529 gdk_flush();
530 gdk_threads_leave();
531
532 omnplay_set_status(app, "");
533
534 gdk_threads_enter();
535 gtk_widget_set_sensitive(app->window, TRUE);
536 gdk_flush();
537 gdk_threads_leave();
538
539
540 return NULL;
541 };
542
543 void omnplay_library_refresh(omnplay_instance_t* app)
544 {
545 if(app->library.refresh_thread)
546 g_thread_join(app->library.refresh_thread);
547
548 app->library.refresh_thread = g_thread_create(
549 omnplay_library_refresh_proc, app, TRUE, NULL);
550 };
551
552 void omnplay_library_draw(omnplay_instance_t* app)
553 {
554 int i;
555 char tc[12];
556 GtkListStore *list_store;
557 GtkTreeIter iter;
558
559 list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(app->library_grid)));
560 gtk_list_store_clear(list_store);
561
562 pthread_mutex_lock(&app->library.lock);
563
564 for(i = 0;i < app->library.count; i++)
565 {
566 gtk_list_store_append(list_store, &iter);
567
568 gtk_list_store_set(list_store, &iter,
569 0, app->library.item[i].id,
570 1, frames2tc(app->library.item[i].dur, 25.0, tc),
571 2, app->library.item[i].title,
572 3, i,
573 4, FALSE,
574 5, "red",
575 -1 );
576 }
577
578 pthread_mutex_unlock(&app->library.lock);
579 };
580
581 static void get_selected_idx_library_proc(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
582 {
583 int idx, *list = (int*)data;
584 gtk_tree_model_get(model, iter, 3, &idx, -1);
585 list[list[0] + 1] = idx;
586 list[0] = list[0] + 1;
587 };
588
589 static int* get_selected_idx_library(omnplay_instance_t* app)
590 {
591 int* list = NULL;
592 GtkTreeSelection *selection;
593
594 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->library_grid));
595 if(selection)
596 {
597 list = (int*)malloc(sizeof(int) * (MAX_LIBRARY_ITEMS + 1));
598 memset(list, 0, sizeof(int) * (MAX_LIBRARY_ITEMS + 1));
599
600 gtk_tree_selection_selected_foreach(
601 selection,
602 get_selected_idx_library_proc,
603 list);
604
605 if(!list[0])
606 {
607 free(list);
608 list = NULL;
609 };
610 };
611
612 return list;
613 };
614
615
616 playlist_item_t* omnplay_library_get_selected(omnplay_instance_t* app, int *count)
617 {
618 int* idxs;
619 playlist_item_t* items = NULL;
620
621 pthread_mutex_lock(&app->library.lock);
622
623 *count = 0;
624
625 idxs = get_selected_idx_library(app);
626
627 if(idxs)
628 {
629 int i;
630
631 /* alloc items */
632 items = (playlist_item_t*)malloc(sizeof(playlist_item_t) * (idxs[0] + 1));
633
634 /* clear last item */
635 memset(&items[idxs[0]], 0, sizeof(playlist_item_t));
636
637 /* copy items */
638 for(i = 0; i < idxs[0]; i++)
639 items[i] = app->library.item[idxs[i + 1]];
640
641 *count = idxs[0];
642 free(idxs);
643 };
644
645 pthread_mutex_unlock(&app->library.lock);
646
647 return items;
648 };
649
650 void omnplay_library_search(omnplay_instance_t* app, int next)
651 {
652 int idx = 0, i;
653 int* idxs;
654 const char *search;
655 GtkTreePath* path;
656
657 pthread_mutex_lock(&app->library.lock);
658
659 idxs = get_selected_idx_library(app);
660 if(idxs) idx = idxs[1];
661 free(idxs);
662
663 if(!next) idx = 0;
664 else idx++;
665
666 search = gtk_entry_get_text(GTK_ENTRY(app->library.search));
667
668 if(search[0])
669 {
670 for(i = idx; i < app->library.count; i++)
671 if( strcasestr(app->library.item[i].id, search) ||
672 strcasestr(app->library.item[i].title, search))
673 break;
674
675 if(i < app->library.count)
676 {
677 g_warning("found at pos=%d\n", i);
678
679 /* select */
680 path = gtk_tree_path_new_from_indices(i, -1);
681 gtk_tree_selection_select_path(gtk_tree_view_get_selection(
682 GTK_TREE_VIEW(app->library_grid)), path);
683 gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->library_grid), path, NULL, FALSE);
684 gtk_tree_path_free(path);
685 };
686 };
687
688 pthread_mutex_unlock(&app->library.lock);
689 };
690
691 #endif