diff --git a/kitty/boss.py b/kitty/boss.py index 77846e86b..4119575ed 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -25,8 +25,7 @@ from .config import ( ) from .config_data import MINIMUM_FONT_SIZE from .constants import ( - appname, cache_dir, config_dir, is_macos, kitty_exe, - supports_primary_selection + appname, config_dir, is_macos, kitty_exe, supports_primary_selection ) from .fast_data_types import ( CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, @@ -1380,10 +1379,6 @@ class Boss: with suppress(FileNotFoundError): os.remove(path) - def create_temp_dir_in_cache(self, prefix: Optional[str] = None, suffix: Optional[str] = None) -> str: - from tempfile import mkdtemp - return mkdtemp(prefix=prefix, suffix=suffix, dir=cache_dir()) - def set_update_check_process(self, process: Optional[PopenType] = None) -> None: if self.update_check_process is not None: with suppress(Exception): diff --git a/kitty/constants.py b/kitty/constants.py index f741fd3bb..77ba219b0 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -128,6 +128,9 @@ defconf = os.path.join(config_dir, 'kitty.conf') @lru_cache(maxsize=2) def cache_dir() -> str: + override: Optional[str] = getattr(cache_dir, 'override_dir', None) + if override: + return override if 'KITTY_CACHE_DIRECTORY' in os.environ: candidate = os.path.abspath(os.environ['KITTY_CACHE_DIRECTORY']) elif is_macos: @@ -139,6 +142,13 @@ def cache_dir() -> str: return candidate +def dir_for_disk_cache() -> str: + from tempfile import mkdtemp + ans = os.path.join(cache_dir(), 'disk-cache') + os.makedirs(ans, exist_ok=True) + return mkdtemp(dir=ans) + + def wakeup() -> None: from .fast_data_types import get_boss b = get_boss() diff --git a/kitty/data-types.c b/kitty/data-types.c index 0509c9bbc..746ba8736 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -192,6 +192,7 @@ static struct PyModuleDef module = { extern int init_LineBuf(PyObject *); extern int init_HistoryBuf(PyObject *); extern int init_Cursor(PyObject *); +extern int init_DiskCache(PyObject *); extern bool init_child_monitor(PyObject *); extern int init_Line(PyObject *); extern int init_ColorProfile(PyObject *); @@ -231,6 +232,7 @@ PyInit_fast_data_types(void) { if (!init_HistoryBuf(m)) return NULL; if (!init_Line(m)) return NULL; if (!init_Cursor(m)) return NULL; + if (!init_DiskCache(m)) return NULL; if (!init_child_monitor(m)) return NULL; if (!init_ColorProfile(m)) return NULL; if (!init_Screen(m)) return NULL; diff --git a/kitty/disk-cache.c b/kitty/disk-cache.c new file mode 100644 index 000000000..a2ec15d91 --- /dev/null +++ b/kitty/disk-cache.c @@ -0,0 +1,146 @@ +/* + * disk-cache.c + * Copyright (C) 2020 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "disk-cache.h" +#include "state.h" +#include "loop-utils.h" +#include + +typedef struct { + PyObject_HEAD + char *path; + pthread_mutex_t lock; + pthread_t write_thread; + bool thread_started, lock_inited, loop_data_inited, shutting_down, fully_initialized; + LoopData loop_data; + PyObject *rmtree; +} DiskCache; + +#define mutex(op) pthread_mutex_##op(&self->lock) + +static PyObject* +new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { + DiskCache *self; + self = (DiskCache*)type->tp_alloc(type, 0); + if (self) { + PyObject *shutil = PyImport_ImportModule("shutil"); + if (!shutil) { Py_CLEAR(self); return NULL; } + self->rmtree = PyObject_GetAttrString(shutil, "rmtree"); + Py_CLEAR(shutil); + if (!self->rmtree) { Py_CLEAR(self); return NULL; } + } + return (PyObject*) self; +} + +static void* +write_loop(void *data) { + DiskCache *self = (DiskCache*)data; + while (!self->shutting_down) { + } + return 0; +} + +static bool +ensure_state(DiskCache *self) { + int ret; + if (self->fully_initialized) return true; + if (!self->loop_data_inited) { + if (!init_loop_data(&self->loop_data)) { PyErr_SetFromErrno(PyExc_OSError); return false; } + self->loop_data_inited = true; + } + + if (!self->lock_inited) { + if ((ret = pthread_mutex_init(&self->lock, NULL)) != 0) { + PyErr_Format(PyExc_OSError, "Failed to create disk cache lock mutex: %s", strerror(ret)); + return false; + } + self->lock_inited = true; + } + + if (!self->thread_started) { + if ((ret = pthread_create(&self->write_thread, NULL, write_loop, self)) != 0) { + PyErr_Format(PyExc_OSError, "Failed to start disk cache write thread with error: %s", strerror(ret)); + return false; + } + self->thread_started = true; + } + + if (!self->path) { + PyObject *kc = NULL, *cache_dir = NULL; + kc = PyImport_ImportModule("kitty.constants"); + if (kc) { + cache_dir = PyObject_CallMethod(kc, "dir_for_disk_cache", NULL); + if (cache_dir) { + self->path = strdup(PyUnicode_AsUTF8(cache_dir)); + if (!self->path) PyErr_NoMemory(); + } + } + Py_CLEAR(kc); Py_CLEAR(cache_dir); + if (PyErr_Occurred()) return false; + } + + self->fully_initialized = true; + return true; +} + +static void +wakeup_write_loop(DiskCache *self) { + if (self->thread_started) wakeup_loop(&self->loop_data, false, "disk_cache_write_loop"); +} + +static void +dealloc(DiskCache* self) { + self->shutting_down = true; + if (self->thread_started) { + wakeup_write_loop(self); + pthread_join(self->write_thread, NULL); + self->thread_started = false; + } + if (self->lock_inited) { + pthread_mutex_destroy(&self->lock); + self->lock_inited = false; + } + if (self->loop_data_inited) { + free_loop_data(&self->loop_data); + self->loop_data_inited = false; + } + + if (self->path) { + PyObject_CallFunction(self->rmtree, "sO", self->path, Py_True); + free(self->path); self->path = NULL; + } + Py_CLEAR(self->rmtree); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +#define PYWRAP0(name) static PyObject* py##name(DiskCache *self, PyObject *args UNUSED) +PYWRAP0(ensure_state) { + ensure_state(self); + Py_RETURN_NONE; +} + +#define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} +static PyMethodDef methods[] = { + MW(ensure_state, METH_NOARGS), + {NULL} /* Sentinel */ +}; + + +PyTypeObject DiskCache_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "fast_data_types.DiskCache", + .tp_basicsize = sizeof(DiskCache), + .tp_dealloc = (destructor)dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "A disk based secure cache", + .tp_methods = methods, + .tp_new = new, +}; + + +INIT_TYPE(DiskCache) +PyObject* create_disk_cache(void) { return new(&DiskCache_Type, NULL, NULL); } diff --git a/kitty/disk-cache.h b/kitty/disk-cache.h new file mode 100644 index 000000000..e6de3a8ec --- /dev/null +++ b/kitty/disk-cache.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2020 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +#include + +PyObject* create_disk_cache(void); diff --git a/kitty/graphics.c b/kitty/graphics.c index 51a2bd571..1991fe50e 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -7,6 +7,7 @@ #include "graphics.h" #include "state.h" +#include "disk-cache.h" #include #include @@ -36,6 +37,8 @@ grman_alloc() { PyErr_NoMemory(); Py_CLEAR(self); return NULL; } + self->disk_cache = create_disk_cache(); + if (!self->disk_cache) { Py_CLEAR(self); return NULL; } return self; } @@ -71,6 +74,7 @@ dealloc(GraphicsManager* self) { free(self->images); } free(self->render_data); + Py_CLEAR(self->disk_cache); Py_TYPE(self)->tp_free((PyObject*)self); } diff --git a/kitty/graphics.h b/kitty/graphics.h index af60edaa2..ee6db055d 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -84,6 +84,7 @@ typedef struct { size_t num_of_below_refs, num_of_negative_refs, num_of_positive_refs; unsigned int last_scrolled_by; size_t used_storage; + PyObject *disk_cache; } GraphicsManager;