/* * rofi * * MIT/X11 License * Copyright © 2013-2022 Qball Cow * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ #include "config.h" #include "history.h" #include "rofi.h" #include "settings.h" #include #include #include #include #include #include #include #include /** * History element */ typedef struct __element { /** Index in history */ long int index; /** Entry */ char *name; } _element; static int __element_sort_func(const void *ea, const void *eb, void *data __attribute__((unused))) { _element *a = *(_element **)ea; _element *b = *(_element **)eb; return b->index - a->index; } static void __history_write_element_list(FILE *fd, _element **list, unsigned int length) { if (list == NULL || length == 0) { return; } // Sort the list before writing out. g_qsort_with_data(list, length, sizeof(_element *), __element_sort_func, NULL); // Get minimum index. int min_value = list[length - 1]->index; // Set the max length of the list. length = (length > config.max_history_size) ? config.max_history_size : length; // Write out entries. for (unsigned int iter = 0; iter < length; iter++) { fprintf(fd, "%ld %s\n", list[iter]->index - min_value, list[iter]->name); } } static char **__history_get_element_list_fields(FILE *fd, unsigned int *length) { unsigned int real_length = 0; char **retv = NULL; ; if (length == NULL) { return NULL; } *length = 0; if (fd == NULL) { return NULL; } char *buffer = NULL; size_t buffer_length = 0; ssize_t l = 0; while ((l = getline(&buffer, &buffer_length, fd)) > 0) { // Jump to the first space. const char *start = strchr(buffer, ' '); // not found, skip. if (start == NULL) { continue; } start++; // remove trailing \n buffer[l - 1] = '\0'; if (real_length < (*length + 2)) { real_length += 15; // Resize and check. retv = g_realloc(retv, (real_length) * sizeof(char *)); } // Parse the number of times. retv[(*length)] = g_strndup(start, l - 1 - (start - buffer)); // Force trailing '\0' retv[(*length) + 1] = NULL; (*length)++; } if (buffer_length > 0) { g_free(buffer); } return retv; } static _element **__history_get_element_list(FILE *fd, unsigned int *length) { unsigned int real_length = 0; _element **retv = NULL; if (length == NULL) { return NULL; } *length = 0; if (fd == NULL) { return NULL; } char *buffer = NULL; size_t buffer_length = 0; ssize_t l = 0; while ((l = getline(&buffer, &buffer_length, fd)) > 0) { char *start = NULL; // Skip empty lines. if (l <= 1) { continue; } long int index = strtol(buffer, &start, 10); if (start == buffer || *start == '\0') { continue; } start++; if ((l - (start - buffer)) < 2) { continue; } if (real_length < (*length + 2)) { real_length += 15; // Resize and check. retv = g_realloc(retv, (real_length) * sizeof(_element *)); } retv[(*length)] = g_malloc(sizeof(_element)); // remove trailing \n buffer[l - 1] = '\0'; // Parse the number of times. retv[(*length)]->index = index; retv[(*length)]->name = g_strndup(start, l - 1 - (start - buffer)); // Force trailing '\0' retv[(*length) + 1] = NULL; (*length)++; } if (buffer != NULL) { free(buffer); buffer = NULL; } return retv; } void history_set(const char *filename, const char *entry) { if (config.disable_history) { return; } // Check if program should be ignored for (char *checked_prefix = strtok(config.ignored_prefixes, ";"); checked_prefix != NULL; checked_prefix = strtok(NULL, ";")) { // For each ignored prefix while (g_unichar_isspace(g_utf8_get_char(checked_prefix))) { checked_prefix = g_utf8_next_char( checked_prefix); // Some users will probably want "; " as their // separator for aesthetics. } if (g_str_has_prefix(entry, checked_prefix)) { return; } } int found = 0; unsigned int curr = 0; unsigned int length = 0; _element **list = NULL; // Open file for reading and writing. FILE *fd = g_fopen(filename, "r"); if (fd != NULL) { // Get list. list = __history_get_element_list(fd, &length); // Close file, if fails let user know on stderr. if (fclose(fd) != 0) { g_warning("Failed to close history file: %s", g_strerror(errno)); } } // Look if the entry exists. for (unsigned int iter = 0; !found && iter < length; iter++) { if (strcmp(list[iter]->name, entry) == 0) { curr = iter; found = 1; } } if (found) { // If exists, increment list index number list[curr]->index++; } else { // If not exists, add it. // Increase list by one list = g_realloc(list, (length + 2) * sizeof(_element *)); list[length] = g_malloc(sizeof(_element)); // Copy name if (list[length] != NULL) { list[length]->name = g_strdup(entry); // set # hits list[length]->index = 1; length++; list[length] = NULL; } } fd = fopen(filename, "w"); if (fd == NULL) { g_warning("Failed to open file: %s", g_strerror(errno)); } else { // Write list. __history_write_element_list(fd, list, length); // Close file, if fails let user know on stderr. if (fclose(fd) != 0) { g_warning("Failed to close history file: %s", g_strerror(errno)); } } // Free the list. for (unsigned int iter = 0; iter < length; iter++) { g_free(list[iter]->name); g_free(list[iter]); } g_free(list); } void history_remove(const char *filename, const char *entry) { if (config.disable_history) { return; } _element **list = NULL; int found = 0; unsigned int curr = 0; unsigned int length = 0; // Open file for reading and writing. FILE *fd = g_fopen(filename, "r"); if (fd == NULL) { g_warning("Failed to open file: %s", g_strerror(errno)); return; } // Get list. list = __history_get_element_list(fd, &length); // Close file, if fails let user know on stderr. if (fclose(fd) != 0) { g_warning("Failed to close history file: %s", g_strerror(errno)); } // Find entry. for (unsigned int iter = 0; !found && iter < length; iter++) { if (strcmp(list[iter]->name, entry) == 0) { curr = iter; found = 1; } } // If found, remove it and write out new file. if (found) { // Remove the entry. g_free(list[curr]->name); g_free(list[curr]); // Swap last to here (if list is size 1, we just swap empty sets). list[curr] = list[length - 1]; // Empty last. list[length - 1] = NULL; length--; fd = g_fopen(filename, "w"); // Clear list. if (fd != NULL) { // Write list. __history_write_element_list(fd, list, length); // Close file, if fails let user know on stderr. if (fclose(fd) != 0) { g_warning("Failed to close history file: %s", g_strerror(errno)); } } else { g_warning("Failed to open file: %s", g_strerror(errno)); } } // Free the list. for (unsigned int iter = 0; iter < length; iter++) { g_free(list[iter]->name); g_free(list[iter]); } if (list != NULL) { g_free(list); } } char **history_get_list(const char *filename, unsigned int *length) { *length = 0; if (config.disable_history) { return NULL; } char **retv = NULL; // Open file. FILE *fd = g_fopen(filename, "r"); if (fd == NULL) { // File that does not exists is not an error, so ignore it. // Everything else? panic. if (errno != ENOENT) { g_warning("Failed to open file: %s", g_strerror(errno)); } return NULL; } // Get list. retv = __history_get_element_list_fields(fd, length); // Close file, if fails let user know on stderr. if (fclose(fd) != 0) { g_warning("Failed to close history file: %s", g_strerror(errno)); } return retv; }