add single instance mode
[uwplayer.git] / application / settings.c
index d651a81..ac45d5d 100644 (file)
 #include <sys/stat.h>
 #include <pthread.h>
 
+#include <sys/socket.h>
+#include <sys/un.h>
+
+
+#include "main.h"
 #include "utils.h"
+#include "json.h"
+#include "window.h"
+#include "playlist.h"
 
-#include <ucx/map.h>
-#include <ucx/properties.h>
+#include <cx/map.h>
+#include <cx/hash_map.h>
+//include <ucx/properties.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/printf.h>
 
 #define CONFIG_BASE_DIR ".config"
 #define UWP_CONFIG_DIR  "uwplayer"
-#define UWP_CONFIG_FILE "uwplayer.properties"
+#define UWP_CONFIG_FILE "uwplayer.conf"
+
+#define JS_READ_BUFSIZE 4096
 
 static void* player_bin_search_thread(void *data);
+static void conf_load_global_settings(void);
 
 static char *uwp_config_dir;
-static UcxMap *uwp_settings; 
+
+static int instance_socket = -1;
+
+/*
+ * root json config object
+ */
+static JSONObject *uwp_config;
+
+/*
+ * global settings from json config converted to key/value pairs
+ */
+static CxMap *uwp_settings; 
+
+/*
+ * default settings
+ */
+static CxMap *uwp_default;
 
 static int check_config_dir(void) {
     char *home = getenv("HOME");
@@ -74,32 +105,64 @@ static int check_config_dir(void) {
     return ret;
 }
 
-int load_settings(void) {
+int load_config(void) {
     if(check_config_dir()) {
         return 1;
     }
     
-    uwp_settings = ucx_map_new(16);
+    uwp_settings = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    uwp_default = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
     
     char *cfgfile_path = util_concat_path(uwp_config_dir, UWP_CONFIG_FILE);
     FILE *cfgfile = fopen(cfgfile_path, "r");
     free(cfgfile_path);
-    if(!cfgfile) return 0;
     
     int ret = 0;
-    if(ucx_properties_load(uwp_settings, cfgfile)) {
-        fprintf(stderr, "Error: Cannot read uwplayer settings\n");
-        ret = 1;
-    }
-    fclose(cfgfile);
-    
-    if(ret) {
-        return ret;
+    if(cfgfile) {
+        JSONParser *parser = json_parser_new();
+        
+        JSONValue *value = NULL;
+        char buf[JS_READ_BUFSIZE];
+        size_t r;
+        
+        while((ret = json_read_value(parser, &value)) >= 0) {
+            if(ret == 0) {
+                r = fread(buf, 1, JS_READ_BUFSIZE, cfgfile);
+                if(r == 0) {
+                    break;
+                }
+                json_parser_fill(parser, buf, r);
+            } else {
+                break;
+            }
+        }
+        
+        json_parser_free(parser);
+        
+        if(value) {
+            if(value->type == JSON_OBJECT) {
+                ret = 0;
+                uwp_config = &value->value.object;
+                conf_load_global_settings();
+            } else {
+                ret = 1;
+            }
+        } else {
+            ret = 1;
+        }
+        
+        
+        fclose(cfgfile);
+
+        if(ret) {
+            return ret;
+        }
     }
     
     // check if mpv or mplayer binaries are configured
-    char *player_bin = ucx_map_cstr_get(uwp_settings, UWP_PLAYER_BIN);
-    char *player_type = ucx_map_cstr_get(uwp_settings, UWP_PLAYER_TYPE);
+    char *player_bin = cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN));
+    char *player_type = cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_TYPE));
     
     if(!player_bin) {
         // try to find the mpv or mplayer binary path
@@ -112,9 +175,264 @@ int load_settings(void) {
     return 0;
 }
 
+static void conf_load_global_settings(void) {
+    JSONValue *settings = json_obj_get(uwp_config, "settings");
+    if(!settings) {
+        return;
+    }
+    
+    if(settings->type != JSON_OBJECT) {
+        fprintf(stderr, "Warning: 'settings' not an object\n");
+        return;
+    }
+    
+    JSONObject *s = &settings->value.object;
+    
+    for(size_t i=0;i<s->size;i++) {
+        JSONObjValue *gs = &s->values[i];
+        if(gs->value->type == JSON_STRING) {
+            cxMapPut(uwp_settings, cx_hash_key_str(gs->name), strdup(gs->value->value.string.string));
+        }
+    }
+}
+
+static char* get_which_output(FILE *f, CxBuffer *buf) {
+    buf->pos = 0;
+    buf->size = 0;
+    cx_stream_copy(f, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
+    if(!pclose(f)) {
+        cxBufferPut(buf, 0);
+        size_t i;
+        for(i=0;i<buf->pos;i++) {
+            if(buf->space[i] == '\n') {
+                buf->space[i] = 0;
+                break;
+            }
+        }
+        return buf->space;
+    }
+    return NULL;
+}
+
+static Boolean finish_bin_search(XtPointer data) {
+    PlayerInfo *playerInfo = data;
+    cxMapPut(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN), playerInfo->bin);
+    cxMapPut(uwp_settings, cx_hash_key_str(UWP_PLAYER_TYPE), playerInfo->type);
+    free(playerInfo);
+    return 0;
+}
+
 static void* player_bin_search_thread(void *data) {
-    // TODO:
-    //printf("search\n");
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 256, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+    
+    FILE *f = popen("which mpv", "r");
+    if(f) {
+        char *bin = get_which_output(f, &buf);
+        if(bin) {
+            PlayerInfo *playerInfo = malloc(sizeof(PlayerInfo));
+            playerInfo->bin = strdup(bin);
+            playerInfo->type = strdup("mpv");
+            AppExecProc(finish_bin_search, playerInfo);
+            
+            cxBufferDestroy(&buf);
+            return NULL;
+        }
+    }
     
+    f = popen("which mplayer", "r");
+    if(f) {
+        char *bin = get_which_output(f, &buf);
+        if(bin) {
+            PlayerInfo *playerInfo = malloc(sizeof(PlayerInfo));
+            playerInfo->bin = strdup(bin);
+            playerInfo->type = strdup("mplayer");
+            AppExecProc(finish_bin_search, playerInfo);
+        }
+    }
+    
+    cxBufferDestroy(&buf);
     return NULL;
 }
+
+char* SettingsGetPlayerBin(void) {
+    return cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN));
+}
+
+
+char *InstanceFilePath(Display *dp) {
+    cxmutstr instance_file = cx_asprintf("instance%s", DisplayString(dp));
+    char *path = util_concat_path(uwp_config_dir, instance_file.ptr);
+    free(instance_file.ptr);
+    return path;
+}
+
+int ConnectToInstance(const char *path) {
+    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if(!fd) {
+        return -1;
+    }
+    
+    size_t path_len = strlen(path);
+    
+    struct sockaddr_un addr;
+    if(path_len > sizeof(addr.sun_path)-1) {
+        return -1;
+    }
+    
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    memcpy(addr.sun_path, path, strlen(path) + 1);
+    
+    if(connect(fd, (struct sockaddr*)&addr, sizeof(addr))) {
+        close(fd);
+        return -1;
+    }
+    
+    return fd;
+}
+
+int CreateSingleInstanceSocket(Display *dp, Bool *already_running) {
+    char *instance_path = InstanceFilePath(dp);
+    size_t instance_path_len = strlen(instance_path);
+    *already_running = 0;
+    
+    // check path
+    struct sockaddr_un addr;
+    if(instance_path_len > sizeof(addr.sun_path)-1) {
+        fprintf(stderr, "instance path '%s' too long for unix domain socket", instance_path);
+        free(instance_path);
+        return 1;
+    }
+    
+    // check if the socket already exists and is open
+    struct stat s;
+    if(!stat(instance_path, &s)) {
+        int fd = ConnectToInstance(instance_path);
+        close(fd);
+        if(fd >= 0) {
+            *already_running = 1;
+            return 0; // instance already running
+        }
+        
+        // instance not running but socket file exists
+        // remove socket before creating a new socket
+        unlink(instance_path);
+    }
+    
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    memcpy(addr.sun_path, instance_path, instance_path_len+1);
+    
+    free(instance_path);
+    instance_socket = socket(AF_UNIX, SOCK_STREAM, 0);
+    if(instance_socket < 0) {
+        fprintf(stderr, "cannot create instance socket: %s", strerror(errno));
+        return 1;
+    }
+    
+    if(bind(instance_socket, (struct sockaddr*)&addr, sizeof(addr))) {
+        fprintf(stderr, "cannot bind instance socket: %s", strerror(errno));
+        return 1;
+    }
+    
+    pthread_t tid;
+    if(pthread_create(&tid, NULL, instance_socket_thread, NULL)) {
+        close(instance_socket);
+        instance_socket = -1;
+        return -1;
+    }
+    pthread_detach(tid);
+    
+    return 0;
+}
+
+static Boolean cmd_open(XtPointer data) {
+    MainWindow *win = GetMainWindow();
+    char *file = data;
+    printf("open %s\n", file);
+    
+    PlayListClear(win);
+    PlayListAddFile(win, file);
+    PlayListPlayTrack(win, win->playlist.tracks->size-1);
+    
+    free(data);
+    return 0;
+}
+
+static void process_msg(CxBuffer *msgbuf, size_t *rpos) {
+    cxstring msg = cx_strn(NULL, 0);
+    for(size_t i=*rpos;i<msgbuf->size;i++) {
+        if(msgbuf->space[i] == '\n') {
+            msg.ptr = msgbuf->space + *rpos;
+            msg.length = i - *rpos;
+            *rpos = i+1;
+            break;
+        }
+    }
+    
+    if(msg.length > 0) {
+        if(cx_strprefix(msg, CX_STR("open "))) {
+            cxstring file = cx_strsubs(msg, 5);
+            cxmutstr mfile = cx_strdup(file);
+            AppExecProc(cmd_open, mfile.ptr);
+        } else {
+            fprintf(stderr, "unknown instance command: {%.*s}\n", (int)msg.length, msg.ptr);
+        }
+        
+        if(*rpos < msgbuf->size) {
+            process_msg(msgbuf, rpos);
+        }
+    }
+}
+
+void* instance_socket_thread(void *data) {
+    listen(instance_socket, 8);
+    
+    CxBuffer msgbuf;
+    cxBufferInit(&msgbuf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+    
+    char buf[1024];
+    
+    while(TRUE) {
+        printf("accept instance socket\n");
+        int fd = accept(instance_socket, NULL, 0);
+        if(fd < 0) {
+            break;
+        }
+        printf("accept instance connection\n");
+        
+        msgbuf.pos = 0;
+        msgbuf.size = 0;
+        size_t rpos = 0;
+        
+        ssize_t r;
+        while((r = read(fd, buf, 1024)) > 0) {
+            cxBufferWrite(buf, 1, r, &msgbuf);
+            process_msg(&msgbuf, &rpos);
+        }
+        
+        printf("close instance connection\n");
+        close(fd);
+    }
+    
+    cxBufferDestroy(&msgbuf);
+    
+    printf("instance socket shutdown\n");
+    
+    return NULL;
+}
+
+void ShutdownInstanceSocket(Display *dp) {
+    if(instance_socket < 0) {
+        return;
+    }
+    
+    shutdown(instance_socket, SHUT_RDWR);
+    close(instance_socket);
+    instance_socket = -1;
+    
+    char *instance_path = InstanceFilePath(dp);
+    unlink(instance_path);
+    free(instance_path);
+}