summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Burke <rich.g.burke@gmail.com>2014-05-11 15:45:22 +0200
committerMarcin Bukat <marcin.bukat@gmail.com>2014-10-09 09:43:40 +0200
commit1e7b93a9b2a0eace355eca39dffafa71d5248744 (patch)
treeba78ace8364dced2d0d216a648cdd1b771bb20f0
parent877bd982a7eff199d001bf2c4178a0c5d970cd45 (diff)
downloadrockbox-1e7b93a9b2a0eace355eca39dffafa71d5248744.tar.gz
rockbox-1e7b93a9b2a0eace355eca39dffafa71d5248744.tar.bz2
rockbox-1e7b93a9b2a0eace355eca39dffafa71d5248744.zip
Fixed disktidy bug and added a couple of new features to disktidy.
The following updates were made to disktidy: - Fixed bug FS#12825. disktidy now checks subdirectories again for files to delete. - Use iterative rather than recursive method to traverse file system. - Once disktidy finishes a run it now returns to it's main menu rather than exiting. - Added "Last Run Stats" view to disktidy. This shows how many files and directories were deleted in the last run as well as the total size of those files, the length of time the run took and when the run took place (for players with RTC). - Added "Playback Control" option to disktidy main menu. Change-Id: I9b7d6d5d08aef2b5f85fb63fcd2ec60f1c1ec2e0 Reviewed-on: http://gerrit.rockbox.org/808 Reviewed-by: Franklin Wei <frankhwei536@gmail.com> Tested: Franklin Wei <frankhwei536@gmail.com> Reviewed-by: Marcin Bukat <marcin.bukat@gmail.com>
-rw-r--r--apps/plugins/disktidy.c496
1 files changed, 416 insertions, 80 deletions
diff --git a/apps/plugins/disktidy.c b/apps/plugins/disktidy.c
index 621238acfb..6f131e37a6 100644
--- a/apps/plugins/disktidy.c
+++ b/apps/plugins/disktidy.c
@@ -18,12 +18,38 @@
* KIND, either express or implied.
*
****************************************************************************/
+
#include "plugin.h"
#include "errno.h"
+#include "lib/playback_control.h"
+#include "lib/display_text.h"
-
-static int removed = 0; /* number of items removed */
-static bool user_abort;
+#define DEFAULT_FILES PLUGIN_APPS_DATA_DIR "/disktidy.config"
+#define CUSTOM_FILES PLUGIN_APPS_DATA_DIR "/disktidy_custom.config"
+#define LAST_RUN_STATS_FILE PLUGIN_APPS_DATA_DIR "/disktidy.stats"
+#define DIR_STACK_SIZE 25
+
+struct dir_info {
+ DIR *dir;
+ int path_length;
+ long size;
+};
+
+/* Store directory info when traversing file system */
+struct dir_stack {
+ struct dir_info dirs[DIR_STACK_SIZE];
+ int size;
+};
+
+struct run_statistics {
+ int files_removed; /* Number of files removed */
+ int dirs_removed; /* Number of directories removed */
+ int run_duration; /* Duration of last run in seconds */
+ double removed_size; /* Size of items removed */
+#if CONFIG_RTC
+ struct tm last_run_time; /* Last time disktidy was run */
+#endif
+};
struct tidy_type {
char filestring[64];
@@ -33,12 +59,41 @@ struct tidy_type {
bool remove;
} tidy_types[64];
+static struct run_statistics run_stats;
static size_t tidy_type_count;
+static bool user_abort;
+static bool tidy_loaded_and_changed = false;
+static bool stats_file_exists = false;
-bool tidy_loaded_and_changed = false;
+static void dir_stack_init(struct dir_stack *dstack)
+{
+ dstack->size = 0;
+}
-#define DEFAULT_FILES PLUGIN_APPS_DATA_DIR "/disktidy.config"
-#define CUSTOM_FILES PLUGIN_APPS_DATA_DIR "/disktidy_custom.config"
+static inline int dir_stack_size(struct dir_stack *dstack)
+{
+ return dstack->size;
+}
+
+static inline bool dir_stack_push(struct dir_stack *dstack, struct dir_info dinfo)
+{
+ if (dstack->size == DIR_STACK_SIZE) {
+ return false;
+ }
+
+ dstack->dirs[dstack->size++] = dinfo;
+ return true;
+}
+
+static inline bool dir_stack_pop(struct dir_stack *dstack, struct dir_info *dinfo)
+{
+ if (dstack->size == 0) {
+ return false;
+ }
+
+ *dinfo = dstack->dirs[--dstack->size];
+ return true;
+}
static void add_item(const char* name, int index)
{
@@ -135,6 +190,127 @@ static void tidy_load_file(const char* file)
rb->close(fd);
}
+static bool save_run_stats(void)
+{
+ int fd = rb->open(LAST_RUN_STATS_FILE, O_WRONLY|O_CREAT, 0666);
+
+ if (fd < 0) {
+ return false;
+ }
+
+ bool save_success = rb->write(fd, &run_stats,
+ sizeof(struct run_statistics)) > 0;
+
+ rb->close(fd);
+
+ return save_success;
+}
+
+static bool load_run_stats(void)
+{
+ int fd = rb->open(LAST_RUN_STATS_FILE, O_RDONLY);
+
+ if (fd < 0) {
+ return false;
+ }
+
+ bool load_success = rb->read(fd, &run_stats,
+ sizeof(struct run_statistics)) == sizeof(struct run_statistics);
+
+ rb->close(fd);
+
+ return load_success;
+}
+
+static enum plugin_status display_run_stats(void)
+{
+ if (!load_run_stats()) {
+ rb->splash(HZ * 2, "Unable to load last run stats");
+ return PLUGIN_OK;
+ }
+
+#if CONFIG_RTC
+ static const char *months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+#endif
+ static const char *size_units[] = {
+ "B", "KB", "MB", "GB", "TB", "PB"
+ };
+
+ int magnitude = 0;
+ double rm_size = run_stats.removed_size;
+
+ while (rm_size >= 1000) {
+ rm_size /= 1024;
+ magnitude++;
+ }
+
+ char total_removed[8];
+ rb->snprintf(total_removed, sizeof(total_removed), "%d",
+ run_stats.files_removed + run_stats.dirs_removed);
+
+ char files_removed[8];
+ rb->snprintf(files_removed, sizeof(files_removed), "%d",
+ run_stats.files_removed);
+
+ char dirs_removed[8];
+ rb->snprintf(dirs_removed, sizeof(dirs_removed), "%d",
+ run_stats.dirs_removed);
+
+ char removed_size[9];
+ rb->snprintf(removed_size, sizeof(removed_size), "%d.%d%s",
+ (int)rm_size, (int)((rm_size - (int)rm_size) * 100),
+ size_units[magnitude]);
+
+ char run_time[9];
+ rb->snprintf(run_time, sizeof(run_time), "%02d:%02d:%02d",
+ run_stats.run_duration / 3600, run_stats.run_duration / 60,
+ run_stats.run_duration % 60);
+
+#if CONFIG_RTC
+ char last_run[18];
+ rb->snprintf(last_run, sizeof(last_run), "%02d:%02d %d/%s/%d",
+ run_stats.last_run_time.tm_hour,
+ run_stats.last_run_time.tm_min, run_stats.last_run_time.tm_mday,
+ months[run_stats.last_run_time.tm_mon],
+ 2000 + (run_stats.last_run_time.tm_year % 100));
+#endif
+
+ char* last_run_text[] = {
+ "Last Run Stats" , "" , "",
+ "Total Removed: ", total_removed, "",
+ "Files Removed: ", files_removed, "",
+ "Dirs Removed: " , dirs_removed , "",
+ "Removed Size: " , removed_size , "",
+ "Run Time: " , run_time , "",
+#if CONFIG_RTC
+ "Run: " , last_run
+#endif
+ };
+
+ static struct style_text display_style[] = {
+ { 0, C_ORANGE | TEXT_CENTER },
+ { 3, C_BLUE },
+ { 6, C_BLUE },
+ { 9, C_BLUE },
+ { 12, C_BLUE },
+ { 15, C_BLUE },
+#if CONFIG_RTC
+ { 18, C_BLUE },
+#endif
+ LAST_STYLE_ITEM
+ };
+
+ if (display_text(ARRAYLEN(last_run_text), last_run_text,
+ display_style, NULL, true)) {
+ return PLUGIN_USB_CONNECTED;
+ }
+
+ return PLUGIN_OK;
+}
+
static bool match(struct tidy_type *tidy_type, const char *string, int len)
{
char *pattern = tidy_type->filestring;
@@ -168,7 +344,8 @@ static void tidy_lcd_status(const char *name)
rb->lcd_puts(0, 0, "Working ...");
rb->lcd_puts(0, 1, name);
#ifdef HAVE_LCD_BITMAP
- rb->lcd_putsf(0, 2, "Cleaned up %d items", removed);
+ rb->lcd_putsf(0, 2, "Cleaned up %d items",
+ run_stats.files_removed + run_stats.dirs_removed);
#endif
rb->lcd_update();
}
@@ -204,65 +381,158 @@ static void tidy_path_remove_entry(char *path, int old_path_length, int *path_le
*path_length = old_path_length;
}
-/* path is assumed to be array of size MAX_PATH. */
-static enum plugin_status tidy_clean(char *path, int *path_length, bool rmdir)
-{
- int old_path_length = *path_length;
+/* Cleanup when user abort or USB event during tidy_clean */
+static void tidy_clean_cleanup(struct dir_stack *dstack, DIR *dir) {
+ struct dir_info dinfo;
- tidy_lcd_status(path);
+ rb->closedir(dir);
- DIR *dir = rb->opendir(path);
- if (!dir)
- return PLUGIN_ERROR;
+ while (dir_stack_pop(dstack, &dinfo)) {
+ rb->closedir(dinfo.dir);
+ }
+}
+/* Perform iterative depth-first search for files to clean */
+static enum plugin_status tidy_clean(char *path, int *path_length) {
+ struct dir_stack dstack;
+ struct dir_info dinfo;
struct dirent *entry;
- while ((entry = rb->readdir(dir)))
- {
- /* check for user input and usb connect */
- int button = rb->get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
- if (button == ACTION_STD_CANCEL)
- {
- rb->closedir(dir);
- user_abort = true;
- return PLUGIN_OK;
- }
- if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
- {
- rb->closedir(dir);
- return PLUGIN_USB_CONNECTED;
- }
-
- rb->yield();
+ struct dirinfo info;
+ DIR *dir, *dir_test;
+ /* Set to true when directory and its contents are to be deleted */
+ bool rm_all = false;
+ /* Used to mark where rm_all starts and ends */
+ int rm_all_start_depth = 0;
+ int button;
+ bool remove;
+ int old_path_length;
- struct dirinfo info = rb->dir_get_info(dir, entry);
- if (!rmdir && !tidy_remove_item(entry->d_name, info.attribute))
- continue;
+ dir_stack_init(&dstack);
+ dir = rb->opendir(path);
- /* get absolute path, returns an error if path is too long */
- if(!tidy_path_append_entry(path, entry, path_length))
- continue; /* silent error */
+ if (!dir) {
+ /* If can't open / then immediately stop */
+ return PLUGIN_ERROR;
+ }
- if (info.attribute & ATTR_DIRECTORY)
- {
- /* dir ignore "." and ".." */
- if (rb->strcmp(entry->d_name, ".") && rb->strcmp(entry->d_name, ".."))
- tidy_clean(path, path_length, true);
- }
- else
- {
- removed++;
- rb->remove(path);
+ dinfo.dir = dir;
+ dinfo.path_length = *path_length;
+ /* Size only used when deleting directory so value here doesn't matter */
+ dinfo.size = 0;
+
+ dir_stack_push(&dstack, dinfo);
+
+ while (dir_stack_pop(&dstack, &dinfo)) {
+ /* Restore path to poped dir */
+ tidy_path_remove_entry(path, dinfo.path_length, path_length);
+ dir = dinfo.dir;
+ tidy_lcd_status(path);
+
+ while ((entry = rb->readdir(dir))) {
+ /* Check for user input and usb connect */
+ button = rb->get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
+
+ if (button == ACTION_STD_CANCEL) {
+ tidy_clean_cleanup(&dstack, dir);
+ user_abort = true;
+ return PLUGIN_OK;
+ }
+ if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
+ tidy_clean_cleanup(&dstack, dir);
+ return PLUGIN_USB_CONNECTED;
+ }
+
+ rb->yield();
+
+ old_path_length = *path_length;
+ info = rb->dir_get_info(dir, entry);
+
+ remove = rm_all || tidy_remove_item(entry->d_name, info.attribute);
+
+ if (info.attribute & ATTR_DIRECTORY) {
+ if (rb->strcmp(entry->d_name, ".") == 0 ||
+ rb->strcmp(entry->d_name, "..") == 0) {
+ continue;
+ }
+
+ if (!remove) {
+ /* Get absolute path, returns an error if path is too long */
+ if (!tidy_path_append_entry(path, entry, path_length)) {
+ continue; /* Silent error */
+ }
+
+ dinfo.dir = dir;
+ dinfo.path_length = old_path_length;
+ dinfo.size = 0;
+
+ /* This directory doesn't need to be deleted, so try to add
+ the current directory we're in to the stack and search
+ this one for files/directories to delete. If we can't
+ add the current directory to the stack or open the new
+ directory to search then continue on in the current
+ directory. */
+ if (dir_stack_push(&dstack, dinfo)) {
+ dir_test = rb->opendir(path);
+
+ if (dir_test) {
+ dir = dir_test;
+ tidy_lcd_status(path);
+ }
+ }
+ }
+ }
+
+ if (!remove) {
+ continue;
+ }
+
+ /* Get absolute path, returns an error if path is too long */
+ if (!tidy_path_append_entry(path, entry, path_length)) {
+ continue; /* Silent error */
+ }
+
+ if (info.attribute & ATTR_DIRECTORY) {
+ /* Remove this directory and all files/directories it contains */
+ dinfo.dir = dir;
+ dinfo.path_length = old_path_length;
+ dinfo.size = info.size;
+
+ if (dir_stack_push(&dstack, dinfo)) {
+ dir_test = rb->opendir(path);
+
+ if (dir_test) {
+ dir = dir_test;
+
+ if (!rm_all) {
+ rm_all = true;
+ rm_all_start_depth = dir_stack_size(&dstack);
+ }
+
+ tidy_lcd_status(path);
+ }
+ }
+ } else {
+ run_stats.files_removed++;
+ run_stats.removed_size += info.size;
+ rb->remove(path);
+
+ /* Restore path */
+ tidy_path_remove_entry(path, old_path_length, path_length);
+ }
}
- /* restore path */
- tidy_path_remove_entry(path, old_path_length, path_length);
- }
- rb->closedir(dir);
+ rb->closedir(dir);
- if (rmdir)
- {
- removed++;
- rb->rmdir(path);
+ if (rm_all) {
+ /* Check if returned to the directory we started rm_all from */
+ if (rm_all_start_depth == dir_stack_size(&dstack)) {
+ rm_all = false;
+ }
+
+ rb->rmdir(path);
+ run_stats.dirs_removed++;
+ run_stats.removed_size += dinfo.size;
+ }
}
return PLUGIN_OK;
@@ -273,18 +543,30 @@ static enum plugin_status tidy_do(void)
/* clean disk and display num of items removed */
char path[MAX_PATH];
+ run_stats.files_removed = 0;
+ run_stats.dirs_removed = 0;
+ run_stats.removed_size = 0;
+ long start_time = *rb->current_tick;
+
+#if CONFIG_RTC
+ run_stats.last_run_time = *rb->get_time();
+#endif
+
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(true);
#endif
rb->strcpy(path, "/");
int path_length = rb->strlen(path);
- enum plugin_status status = tidy_clean(path, &path_length, false);
+ enum plugin_status status = tidy_clean(path, &path_length);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
+ run_stats.run_duration = (*rb->current_tick - start_time) / HZ;
+ stats_file_exists = save_run_stats();
+
if (status == PLUGIN_OK)
{
rb->lcd_clear_display();
@@ -293,8 +575,11 @@ static enum plugin_status tidy_do(void)
rb->splash(HZ, "User aborted");
rb->lcd_clear_display();
}
- rb->splashf(HZ*2, "Cleaned up %d items", removed);
+ rb->lcd_update();
+ rb->splashf(HZ*2, "Cleaned up %d items",
+ run_stats.files_removed + run_stats.dirs_removed);
}
+
return status;
}
@@ -349,30 +634,75 @@ static int list_action_callback(int action, struct gui_synclist *lists)
return ACTION_REDRAW;
}
-static void tidy_lcd_menu(void)
+static bool tidy_types_selected(void)
+{
+ for (unsigned int i = 0; i < tidy_type_count; i++) {
+ if (tidy_types[i].filestring[0] != '<' && tidy_types[i].remove) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int disktidy_menu_cb(int action, const struct menu_item_ex *this_item)
+{
+ int item = ((intptr_t)this_item);
+
+ if (action == ACTION_REQUEST_MENUITEM &&
+ !stats_file_exists && item == 2) {
+
+ return ACTION_EXIT_MENUITEM;
+ }
+
+ return action;
+}
+
+static enum plugin_status tidy_lcd_menu(void)
{
+ enum plugin_status disktidy_status = PLUGIN_OK;
+ bool exit = false;
int selection = 0;
struct simplelist_info list;
- MENUITEM_STRINGLIST(menu, "Disktidy Menu", NULL, "Start Cleaning",
- "Files to Clean", "Quit");
-
- for(;;)
- switch(rb->do_menu(&menu, &selection, NULL, false))
- {
- default:
- user_abort = true;
- case 0:
- return; /* start cleaning */
-
- case 1:
- rb->simplelist_info_init(&list, "Files to Clean", tidy_type_count, NULL);
- list.get_icon = get_icon;
- list.get_name = get_name;
- list.action_callback = list_action_callback;
- rb->simplelist_show_list(&list);
- break;
+ MENUITEM_STRINGLIST(menu, "Disktidy Menu", disktidy_menu_cb,
+ "Start Cleaning", "Files to Clean", "Last Run Stats",
+ "Playback Control", "Quit");
+
+ while (!exit && disktidy_status == PLUGIN_OK) {
+ switch(rb->do_menu(&menu, &selection, NULL, false)) {
+ case 0:
+ if (tidy_types_selected()) {
+ disktidy_status = tidy_do();
+ } else {
+ rb->splash(HZ * 2, "Select at least one file type to clean");
+ }
+
+ break;
+ case 1:
+ rb->simplelist_info_init(&list, "Files to Clean",
+ tidy_type_count, NULL);
+ list.get_icon = get_icon;
+ list.get_name = get_name;
+ list.action_callback = list_action_callback;
+ rb->simplelist_show_list(&list);
+ break;
+ case 2:
+ disktidy_status = display_run_stats();
+ break;
+ case 3:
+ if (playback_control(NULL)) {
+ disktidy_status = PLUGIN_USB_CONNECTED;
+ }
+
+ break;
+ default:
+ exit = true;
+ break;
}
+ }
+
+ return disktidy_status;
}
/* Creates a file and writes information about what files to
@@ -400,14 +730,20 @@ enum plugin_status plugin_start(const void* parameter)
tidy_load_file(DEFAULT_FILES);
tidy_load_file(CUSTOM_FILES);
+
if (tidy_type_count == 0)
{
rb->splash(3*HZ, "Missing disktidy.config file");
return PLUGIN_ERROR;
}
- tidy_lcd_menu();
- if (tidy_loaded_and_changed)
+
+ stats_file_exists = rb->file_exists(LAST_RUN_STATS_FILE);
+
+ enum plugin_status disktidy_status = tidy_lcd_menu();
+
+ if (tidy_loaded_and_changed) {
save_config();
+ }
- return user_abort ? PLUGIN_OK : tidy_do();
+ return disktidy_status;
}