diff --git a/applications/playlist/playlist.c b/applications/playlist/playlist.c index 4a70a98d6..9dd9547de 100644 --- a/applications/playlist/playlist.c +++ b/applications/playlist/playlist.c @@ -11,6 +11,11 @@ #include #include +#include "flipper_format_stream.h" +#include "flipper_format_stream_i.h" + +#include "playlist_file.h" + #define PLAYLIST_FOLDER "/ext/playlist" #define PLAYLIST_EXT ".txt" #define TAG "Playlist" @@ -20,14 +25,27 @@ #define WIDTH 128 #define HEIGHT 64 +typedef struct { + int current_count; // Number of processed files + int total_count; // Number of items in the playlist + + // last 3 files + string_t prev_0_path; // current file + string_t prev_1_path; // previous file + string_t prev_2_path; + string_t prev_3_path; +} DisplayMeta; + typedef struct { FuriThread* thread; Storage* storage; FlipperFormat* format; + DisplayMeta* meta; + string_t file_path; // Path to the playlist file - string_t current_file; // Path to the current file - bool running; // True if the worker is running + bool running; // indicates if the worker is running + bool paused; // can be set to true to pause worker } PlaylistWorker; typedef struct { @@ -36,6 +54,7 @@ typedef struct { ViewPort* view_port; Gui* gui; + DisplayMeta* meta; PlaylistWorker* worker; string_t file_path; // Path to the playlist file @@ -47,57 +66,84 @@ typedef struct { static int32_t playlist_worker_thread(void* ctx) { PlaylistWorker* worker = ctx; - FURI_LOG_I(TAG, "(worker) Worker start"); - if(!flipper_format_file_open_existing(worker->format, string_get_cstr(worker->file_path))) { - FURI_LOG_E(TAG, "(worker) Could not open file %s", string_get_cstr(worker->file_path)); worker->running = false; return 0; } - FURI_LOG_I(TAG, "(worker) Opened file %s", string_get_cstr(worker->file_path)); + // reset worker meta string_t data; string_init(data); - - int count = 0; - - Stream* stream = flipper_format_get_raw_stream(worker->format); - while(worker->running && stream_read_line(stream, data)) { - string_strim(data); - FURI_LOG_I(TAG, "(worker) Read line %s", string_get_cstr(data)); - - char* str; - str = strstr(string_get_cstr(data), "SUB: "); - if(str != NULL) { - str = strchr(str, ' '); - - while(strchr(str, ' ') != NULL) { - str = strchr(str, ' '); - str += 1; - - count++; - FURI_LOG_I(TAG, "(worker) data %d: %s", count, str); - - // show current file - string_set_str(worker->current_file, str); - FURI_LOG_I(TAG, "(worker) current_file: %s", worker->current_file); - - furi_delay_ms(3000); // TODO: remove this delay - } + while(worker->running && flipper_format_read_string(worker->format, "sub", data)) { + // wait if paused + while(worker->paused) { + furi_delay_ms(100); } + + // send .sub files + ++worker->meta->current_count; + const char* str = string_get_cstr(data); + FURI_LOG_I(TAG, "(worker) data #%d: %s", worker->meta->current_count, str); + + // it's not fancy, but it works :) + string_reset(worker->meta->prev_3_path); + string_set_str(worker->meta->prev_3_path, string_get_cstr(worker->meta->prev_2_path)); + string_reset(worker->meta->prev_2_path); + string_set_str(worker->meta->prev_2_path, string_get_cstr(worker->meta->prev_1_path)); + string_reset(worker->meta->prev_1_path); + string_set_str(worker->meta->prev_1_path, string_get_cstr(worker->meta->prev_0_path)); + string_reset(worker->meta->prev_0_path); + string_set_str(worker->meta->prev_0_path, str); + + FURI_LOG_I(TAG, ""); + FURI_LOG_I(TAG, "(worker) prev_3: %s", string_get_cstr(worker->meta->prev_3_path)); + FURI_LOG_I(TAG, "(worker) prev_2: %s", string_get_cstr(worker->meta->prev_2_path)); + FURI_LOG_I(TAG, "(worker) prev_1: %s", string_get_cstr(worker->meta->prev_1_path)); + FURI_LOG_I(TAG, "(worker) prev_0: %s", string_get_cstr(worker->meta->prev_0_path)); + FURI_LOG_I(TAG, ""); + + furi_delay_ms(1500); // TODO: remove this delay } flipper_format_file_close(worker->format); - string_clear(data); - FURI_LOG_I(TAG, "Done reading. Read %d data lines.", count); + FURI_LOG_I(TAG, "Done reading. Read %d data lines.", worker->meta->current_count); worker->running = false; - return 0; } -PlaylistWorker* playlist_worker_alloc() { +//////////////////////////////////////////////////////////////////////////////// + +void playlist_meta_reset(DisplayMeta* instance) { + instance->current_count = 0; + string_clear(instance->prev_0_path); + string_clear(instance->prev_1_path); + string_clear(instance->prev_2_path); + string_clear(instance->prev_3_path); +} + +DisplayMeta* playlist_meta_alloc() { + DisplayMeta* instance = malloc(sizeof(DisplayMeta)); + string_init(instance->prev_0_path); + string_init(instance->prev_1_path); + string_init(instance->prev_2_path); + string_init(instance->prev_3_path); + playlist_meta_reset(instance); + return instance; +} + +void playlist_meta_free(DisplayMeta* instance) { + string_clear(instance->prev_0_path); + string_clear(instance->prev_1_path); + string_clear(instance->prev_2_path); + string_clear(instance->prev_3_path); + free(instance); +} + +//////////////////////////////////////////////////////////////////////////////// + +PlaylistWorker* playlist_worker_alloc(DisplayMeta* meta) { PlaylistWorker* instance = malloc(sizeof(PlaylistWorker)); instance->thread = furi_thread_alloc(); @@ -108,9 +154,11 @@ PlaylistWorker* playlist_worker_alloc() { instance->storage = furi_record_open(RECORD_STORAGE); instance->format = flipper_format_file_alloc(instance->storage); + instance->meta = meta; + + instance->paused = true; // require the user to manually start the worker string_init(instance->file_path); - string_init(instance->current_file); return instance; } @@ -123,7 +171,6 @@ void playlist_worker_free(PlaylistWorker* instance) { furi_record_close(RECORD_STORAGE); string_clear(instance->file_path); - string_clear(instance->current_file); free(instance); } @@ -148,6 +195,9 @@ void playlist_worker_start(PlaylistWorker* instance, const char* file_path) { string_set_str(instance->file_path, file_path); instance->running = true; + // reset meta (current/total) + playlist_meta_reset(instance->meta); + furi_thread_start(instance->thread); } @@ -161,32 +211,52 @@ static void render_callback(Canvas* canvas, void* ctx) { switch(app->state) { case STATE_OVERVIEW: - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, HEIGHT - 5, AlignLeft, AlignBottom, "FILE:"); - - // extract file name from file_path + // draw progress bar { - int padL = canvas_string_width(canvas, "FILE: "); + double progress = (double)app->meta->current_count / (double)app->meta->total_count; + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 1, HEIGHT - 12, WIDTH - 2, 11, 2); - string_t file_name; - string_init(file_name); - path_extract_filename(app->file_path, file_name, true); + if(progress > 0) { + int progress_width = (int)(progress * (double)(WIDTH - 2)); + canvas_draw_rbox(canvas, 1, HEIGHT - 12, progress_width, 11, 2); + } + + // draw progress text + string_t progress_text; + string_init(progress_text); + string_printf( + progress_text, "%d/%d", app->meta->current_count, app->meta->total_count); + + if(progress >= (double).5) { + canvas_set_color(canvas, ColorWhite); + } else { + canvas_set_color(canvas, ColorBlack); + } canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned( - canvas, 5 + padL, HEIGHT - 5, AlignLeft, AlignBottom, string_get_cstr(file_name)); - string_clear(file_name); + canvas, + WIDTH / 2, + HEIGHT - 3, + AlignCenter, + AlignBottom, + string_get_cstr(progress_text)); + + string_clear(progress_text); } - if(app->worker != NULL && app->worker->running == true && - !string_empty_p(app->worker->current_file)) { - string_t file_name; - string_init(file_name); - path_extract_filename(app->worker->current_file, file_name, true); - canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, string_get_cstr(file_name)); - FURI_LOG_I(TAG, "(render) drawing current file %s", string_get_cstr(file_name)); - string_clear(file_name); + // draw controls + { + canvas_set_font(canvas, FontSecondary); + canvas_set_color(canvas, ColorBlack); + if(!app->worker->running) { + canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Start"); + } else if(app->worker->paused) { + canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Resume"); + } else { + canvas_draw_str_aligned(canvas, 5, 5, AlignLeft, AlignTop, "[OK]: Pause"); + } } - break; } @@ -200,11 +270,14 @@ static void input_callback(InputEvent* event, void* ctx) { //////////////////////////////////////////////////////////////////////////////// -Playlist* playlist_alloc() { +Playlist* playlist_alloc(DisplayMeta* meta) { Playlist* app = malloc(sizeof(Playlist)); app->state = 0; - string_init(app->file_path); + string_init(app->file_path); + string_set_str(app->file_path, PLAYLIST_FOLDER); + + app->meta = meta; app->worker = NULL; app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); @@ -232,76 +305,102 @@ void playlist_free(Playlist* app) { furi_message_queue_free(app->input_queue); furi_mutex_free(app->mutex); + playlist_meta_free(app->meta); + free(app); } int32_t playlist_app(void* p) { UNUSED(p); - // create app - Playlist* app = playlist_alloc(); - // create playlist folder - Storage* storage = furi_record_open(RECORD_STORAGE); - if(!storage_simply_mkdir(storage, PLAYLIST_FOLDER)) { - FURI_LOG_E(TAG, "Could not create folder %s", PLAYLIST_FOLDER); + { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(!storage_simply_mkdir(storage, PLAYLIST_FOLDER)) { + FURI_LOG_E(TAG, "Could not create folder %s", PLAYLIST_FOLDER); + } + furi_record_close(RECORD_STORAGE); } - furi_record_close(RECORD_STORAGE); - string_set_str(app->file_path, PLAYLIST_FOLDER); + // create app + DisplayMeta* meta = playlist_meta_alloc(); + Playlist* app = playlist_alloc(meta); // select playlist file - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - const bool res = dialog_file_browser_show( - dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); - furi_record_close(RECORD_DIALOGS); - - do { + { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + const bool res = dialog_file_browser_show( + dialogs, app->file_path, app->file_path, PLAYLIST_EXT, true, &I_sub1_10px, true); + furi_record_close(RECORD_DIALOGS); // check if a file was selected if(!res) { FURI_LOG_E(TAG, "No file selected"); + goto exit_cleanup; + } + } + + //////////////////////////////////////////////////////////////////////////////// + + FURI_LOG_I(TAG, "Starting thread ..."); + app->worker = playlist_worker_alloc(meta); + + // count playlist items + { + Storage* storage = furi_record_open(RECORD_STORAGE); + app->meta->total_count = + playlist_count_playlist_items(storage, string_get_cstr(app->file_path)); + FURI_LOG_I(TAG, "Selected file contains %d playlist items.", app->meta->total_count); + furi_record_close(RECORD_STORAGE); + } + + // start thread + playlist_worker_start(app->worker, string_get_cstr(app->file_path)); + + app->state = STATE_OVERVIEW; + + bool exit_loop = false; + InputEvent input; + while(1) { // close application if no file was selected + FURI_LOG_I(TAG, "Checking queue"); + furi_check( + furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); + + FURI_LOG_I( + TAG, + "Key: %s, Type: %s", + input_get_key_name(input.key), + input_get_type_name(input.type)); + + switch(input.key) { + case InputKeyOk: + // toggle pause state + if(!app->worker->running) { + FURI_LOG_I(TAG, "Worker is NOT running. Starting worker."); + playlist_worker_start(app->worker, string_get_cstr(app->file_path)); + } else { + FURI_LOG_I(TAG, "Worker IS running. Toggled pause state."); + app->worker->paused = !app->worker->paused; + } + break; + case InputKeyBack: + FURI_LOG_I(TAG, "Pressed Back button. Application will exit"); + exit_loop = true; + break; + default: break; } - app->state = STATE_OVERVIEW; + furi_mutex_release(app->mutex); - FURI_LOG_I(TAG, "Starting thread ..."); - app->worker = playlist_worker_alloc(); - playlist_worker_start(app->worker, string_get_cstr(app->file_path)); - - bool exit_loop = false; - InputEvent input; - while(res) { // close application if no file was selected - FURI_LOG_I(TAG, "Checking queue"); - furi_check( - furi_message_queue_get(app->input_queue, &input, FuriWaitForever) == FuriStatusOk); - - FURI_LOG_I( - TAG, - "Key: %s, Type: %s", - input_get_key_name(input.key), - input_get_type_name(input.type)); - - switch(input.key) { - case InputKeyBack: - FURI_LOG_I(TAG, "Pressed Back button. Application will exit"); - exit_loop = true; - break; - default: - break; - } - - furi_mutex_release(app->mutex); - - // exit application - if(exit_loop == true) { - break; - } - - view_port_update(app->view_port); + // exit application + if(exit_loop == true) { + break; } - } while(0); + view_port_update(app->view_port); + } + +exit_cleanup: if(app->worker != NULL) { if(playlist_worker_running(app->worker)) { FURI_LOG_I(TAG, "Thread is still running. Requesting thread to finish ..."); diff --git a/applications/playlist/playlist_file.c b/applications/playlist/playlist_file.c new file mode 100644 index 000000000..64d50d0ad --- /dev/null +++ b/applications/playlist/playlist_file.c @@ -0,0 +1,20 @@ +#include + +#include +#include + +int playlist_count_playlist_items(Storage* storage, const char* file_path) { + FlipperFormat* format = flipper_format_file_alloc(storage); + if(!flipper_format_file_open_existing(format, file_path)) { + return -1; + } + int count = 0; + string_t data; + string_init(data); + while(flipper_format_read_string(format, "sub", data)) { + ++count; + } + flipper_format_file_close(format); + string_clear(data); + return count; +} \ No newline at end of file diff --git a/applications/playlist/playlist_file.h b/applications/playlist/playlist_file.h new file mode 100644 index 000000000..53577f7d9 --- /dev/null +++ b/applications/playlist/playlist_file.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include + +int playlist_count_playlist_items(Storage* storage, const char* file_path); \ No newline at end of file