diff options
| author | 2024-01-27 20:39:25 -0500 | |
|---|---|---|
| committer | 2024-01-27 20:39:25 -0500 | |
| commit | b100eb175e0cfa59a15c4125f26ab5474c2347ec (patch) | |
| tree | f1ee4f8bed5d3490ba5d70a6fc194f3206df8df4 | |
| parent | fb1cbf546300d9f8efdf21c6ac66f7c2f1dfd4f3 (diff) | |
| download | camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.tar.gz camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.tar.bz2 camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.zip | |
wip
- Mostly work in the client
Signed-off-by: Andrew Opalach <andrew@akon.city>
| -rw-r--r-- | src/bimu/vcr.c | 4 | ||||
| -rw-r--r-- | src/bimu/vcr.h | 2 | ||||
| -rw-r--r-- | src/buffer/audio.c | 2 | ||||
| -rw-r--r-- | src/buffer/clock.c | 4 | ||||
| -rw-r--r-- | src/buffer/frame_queue.h | 2 | ||||
| -rw-r--r-- | src/buffer/meson.build | 6 | ||||
| -rw-r--r-- | src/buffer/peak_buffer.c | 2 | ||||
| -rw-r--r-- | src/buffer/video.c | 3 | ||||
| -rw-r--r-- | src/cache/handlers/http.c | 26 | ||||
| -rw-r--r-- | src/codec/libav/decoder.c | 1 | ||||
| -rw-r--r-- | src/codec/libav/demuxer.c | 8 | ||||
| -rw-r--r-- | src/fruits/cmc/cmc.c | 901 | ||||
| -rw-r--r-- | src/fruits/sink/sink.c | 4 | ||||
| -rw-r--r-- | src/libclient/client.c | 97 | ||||
| -rw-r--r-- | src/libclient/client.h | 26 | ||||
| -rw-r--r-- | src/libclient/commands.h | 1 | ||||
| -rw-r--r-- | src/libclient/resource_client.c | 2 | ||||
| -rw-r--r-- | src/libsink/common.h | 3 | ||||
| -rw-r--r-- | src/libsink/sink.c | 247 | ||||
| -rw-r--r-- | src/libsink/sink.h | 2 | ||||
| -rw-r--r-- | src/screen/screen.c | 3 | ||||
| -rw-r--r-- | src/tree/common.h | 13 | ||||
| -rw-r--r-- | src/tree/list.c | 178 | ||||
| -rw-r--r-- | src/tree/list.h | 13 | ||||
| -rw-r--r-- | src/tree/tree.c | 97 | ||||
| -rw-r--r-- | src/tree/tree.h | 2 |
26 files changed, 1080 insertions, 569 deletions
diff --git a/src/bimu/vcr.c b/src/bimu/vcr.c index c5bb0e8..fcef62e 100644 --- a/src/bimu/vcr.c +++ b/src/bimu/vcr.c @@ -57,7 +57,7 @@ void bmu_vcr_add_stream(struct bmu_vcr *vcr, struct bmu_vcr_stream *stream) aki_cond_init(&stream->cond); aki_mutex_init(&stream->mutex); aki_packet_cache_init(&stream->cache, VCR_BUFFER_HIGH); - stream->buffered = false; + stream->buffered = 0; al_array_push(vcr->streams, stream); al_atomic_s32_store(&stream->state, BIMU_STREAM_RUNNING, AL_ATOMIC_RELAXED); } @@ -87,7 +87,7 @@ bool bmu_vcr_push_packet(struct bmu_vcr *vcr, struct aki_packet *packet) } else if (al_atomic_u64_add(&vcr->count, 1, AL_ATOMIC_RELAXED) >= VCR_BUFFER_BUFFERED) { aki_packet_stream_cork(vcr->data, true); al_array_foreach(vcr->streams, i, stream) { - stream->buffered = true; + stream->buffered = 1; } } break; diff --git a/src/bimu/vcr.h b/src/bimu/vcr.h index 5a6d8d5..0f81c12 100644 --- a/src/bimu/vcr.h +++ b/src/bimu/vcr.h @@ -20,7 +20,7 @@ struct bmu_vcr_stream { struct bmu_client_handler *client; atomic_s32 state; struct aki_packet_cache cache; - bool buffered; + s32 buffered; struct aki_cond cond; struct aki_mutex mutex; bool running; diff --git a/src/buffer/audio.c b/src/buffer/audio.c index a1a109e..ce7a697 100644 --- a/src/buffer/audio.c +++ b/src/buffer/audio.c @@ -5,7 +5,7 @@ #define BUFFER_USEC (12 * 1000000L) #define BUFFER_WATERMARK_LOW (3 * 1000000L) // Must be a most half of the buffer size. -#define BUFFER_WATERMARK_HIGH (4 * 1000000L) +#define BUFFER_WATERMARK_HIGH (3 * 1000000L) #define DESYNC_PTS 0.022 diff --git a/src/buffer/clock.c b/src/buffer/clock.c index 1e7883c..f8284ec 100644 --- a/src/buffer/clock.c +++ b/src/buffer/clock.c @@ -14,10 +14,10 @@ bool camu_clock_calc_tick(struct camu_clock *clock) { u64 start = al_atomic_u64_load(&clock->start, AL_ATOMIC_RELAXED); if (start == 0L) return false; - u64 ts = clock->delay == 0 ? start : aki_get_timestamp(); + f64 tick = al_atomic_f64_load(&clock->tick, AL_ATOMIC_RELAXED); + u64 ts = (tick == 0.0 && clock->delay == 0) ? start : aki_get_timestamp(); al_assert(start <= ts); u64 diff = ts - start; - f64 tick = al_atomic_f64_load(&clock->tick, AL_ATOMIC_RELAXED); if (tick == 0.0) { if (!(diff >= clock->delay && clock->delay >= (diff - clock->delay))) { // We are past the requested start time. diff --git a/src/buffer/frame_queue.h b/src/buffer/frame_queue.h index 35a84e4..df58497 100644 --- a/src/buffer/frame_queue.h +++ b/src/buffer/frame_queue.h @@ -6,8 +6,6 @@ #include "../codec/codec.h" -#include "common.h" - enum { CAMU_QUEUE_OK = 0, CAMU_QUEUE_MORE, diff --git a/src/buffer/meson.build b/src/buffer/meson.build index dce16de..d037d6b 100644 --- a/src/buffer/meson.build +++ b/src/buffer/meson.build @@ -1,4 +1,8 @@ -buffer_src = ['video.c', 'audio.c', 'clock.c', 'peak_buffer.c'] +no_video = false +buffer_src = ['audio.c', 'clock.c', 'peak_buffer.c'] +if not no_video + buffer_src += ['video.c'] +endif buffer_deps = [common_deps] buffer = declare_dependency(sources: buffer_src, dependencies: buffer_deps) diff --git a/src/buffer/peak_buffer.c b/src/buffer/peak_buffer.c index 22a007d..2856a78 100644 --- a/src/buffer/peak_buffer.c +++ b/src/buffer/peak_buffer.c @@ -1,5 +1,7 @@ #include "peak_buffer.h" +// https://github.com/MusicPlayerDaemon/MPD/blob/c71e586c530e1d066efde7c2ca40c363f74100c7/src/util/PeakBuffer.cxx + void camu_peak_buffer_init(struct camu_peak_buffer *buf) { aki_buffer_init(&buf->buf); diff --git a/src/buffer/video.c b/src/buffer/video.c index 9a04933..15af78e 100644 --- a/src/buffer/video.c +++ b/src/buffer/video.c @@ -1,11 +1,12 @@ #include <al/log.h> #include "video.h" +#include "common.h" //#define CAMU_VIDEO_BUFFER_FORCE_SCALER #define BUFFER_WATERMARK_LOW 8 // frames. -#define BUFFER_WATERMARK_BUFFERED 10 +#define BUFFER_WATERMARK_BUFFERED 8 #define BUFFER_WATERMARK_HIGH 14 #define BUFFER_WATERMARK_RESET BUFFER_WATERMARK_HIGH + 10. diff --git a/src/cache/handlers/http.c b/src/cache/handlers/http.c index dd3c8ac..97abf79 100644 --- a/src/cache/handlers/http.c +++ b/src/cache/handlers/http.c @@ -14,6 +14,19 @@ static bool handler_http_can_seek(struct cch_handler *handler) return false; } +static void disable_waiters_internal(struct cch_handler_http *http) +{ + aki_mutex_lock(&http->mutex); + struct cch_handler_wait *wait; + al_array_foreach(http->handler.waits, i, wait) { + aki_mutex_lock(&wait->mutex); + wait->disabled = true; + aki_cond_signal(&wait->cond); + aki_mutex_unlock(&wait->mutex); + } + aki_mutex_unlock(&http->mutex); +} + static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0) { struct cch_handler_http *http = (struct cch_handler_http *)userdata; @@ -24,8 +37,9 @@ static size_t http_callback(void *userdata, u8 op, u8 *buf, s64 int0) break; } case AKI_HTTP_ERROR: { + disable_waiters_internal(http); // TODO: Untested. al_log_debug("cache_handler_http", "Error."); - break; // Unhandled. + break; } case AKI_HTTP_RESPONSE_CODE: { al_log_debug("cache_handler_http", "HTTP %ld.", int0); @@ -141,15 +155,7 @@ static bool handler_http_wait_for_range(struct cch_handler *handler, struct cch_ static void handler_http_free(struct cch_handler **handler) { struct cch_handler_http *http = (struct cch_handler_http *)*handler; - aki_mutex_lock(&http->mutex); - struct cch_handler_wait *wait; - al_array_foreach(http->handler.waits, i, wait) { - aki_mutex_lock(&wait->mutex); - wait->disabled = true; - aki_cond_signal(&wait->cond); - aki_mutex_unlock(&wait->mutex); - } - aki_mutex_unlock(&http->mutex); + disable_waiters_internal(http); camu_queue_push(http->queue, -1); aki_signal_send(&http->signal); aki_thread_join(&http->thread); diff --git a/src/codec/libav/decoder.c b/src/codec/libav/decoder.c index 3a6ac06..e732be9 100644 --- a/src/codec/libav/decoder.c +++ b/src/codec/libav/decoder.c @@ -69,7 +69,6 @@ static bool lav_decoder_init(struct camu_decoder *dec, struct camu_renderer *ren return true; err: close_internal(av); - return false; } diff --git a/src/codec/libav/demuxer.c b/src/codec/libav/demuxer.c index fb05ec8..e276865 100644 --- a/src/codec/libav/demuxer.c +++ b/src/codec/libav/demuxer.c @@ -72,16 +72,16 @@ static bool lav_demuxer_init(struct camu_demuxer *demux, struct cch_handle *hand enum AVMediaType type = stream->codecpar->codec_type; // Try to detect attached images. if (type == AVMEDIA_TYPE_VIDEO && GUESS_STREAM_IS_IMAGE(stream)) { - al_log_info("lav_demux", "Assuming stream #%i is an image.", i); + al_log_info("lav_demux", "Guessing that stream #%u is an image.", i); stream->duration = 0; } else if (stream->duration < 0) { - al_log_warn("lav_demux", "Stream #%i has invalid duration (%ld).", av->format_context->duration); + al_log_warn("lav_demux", "Stream #%u has an invalid duration (%ld).", i, av->format_context->duration); if (av->format_context->duration < 0) { stream->duration = 0; } else { stream->duration = av_rescale_q(av->format_context->duration, AV_TIME_BASE_Q, stream->time_base); } - al_log_info("lav_demux", "Setting stream #%i to a duration of %.3fs.", i, stream->duration * av_q2d(stream->time_base)); + al_log_info("lav_demux", "Setting stream #%u to a duration of %.3fs.", i, stream->duration * av_q2d(stream->time_base)); } if (stream->start_time < 0) stream->start_time = 0; s64 duration = av_rescale_q(stream->duration, stream->time_base, AV_TIME_BASE_Q); @@ -102,7 +102,6 @@ static bool lav_demuxer_init(struct camu_demuxer *demux, struct cch_handle *hand return true; err: close_internal(av); - return false; } @@ -128,6 +127,7 @@ static s32 lav_demuxer_get_packet(struct camu_demuxer *demux, struct camu_packet } if (ret < 0) { al_log_error("lav_demuxer", "Failed to read frame (%s).", av_err2str(ret)); + continue; } if (!subscribed_to_index(demux, packet->av.pkt->stream_index)) { av_packet_unref(packet->av.pkt); diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c index 89800f6..55f659c 100644 --- a/src/fruits/cmc/cmc.c +++ b/src/fruits/cmc/cmc.c @@ -1,4 +1,5 @@ #include <al/lib.h> +#include <al/log.h> #include <al/wstr.h> #include <al/random.h> #include <aki/common.h> @@ -14,17 +15,27 @@ #include "../../tree/common.h" enum { - CMC_LIST_SEARCHES = 0, - CMC_CREATE_SEARCH, - CMC_OPEN_SEARCH, - CMC_LIST_LISTS, - CMC_CREATE_LIST + CMC_LIST = 0, + CMC_SEARCH +}; + +enum { + CMC_ACTION_OPEN = 0, + CMC_ACTION_LIST, + CMC_ACTION_CREATE, +}; + +enum { + CMC_VIEW_NONE = 0, + CMC_VIEW_SINGLE, + CMC_VIEW_PARTITION }; struct cmc_view { struct sho_post *post; struct ncplane *container; - bool has_visual_data; + u32 rows; + u32 y; }; struct cmc_view_resource { @@ -36,13 +47,7 @@ struct cmc_view_resource { struct ncvisual_options vopts; }; -enum { - CMC_VIEW_NONE = 0, - CMC_VIEW_SINGLE, - CMC_VIEW_PARTITION -}; - -struct cmc_twitter_view { +struct cmc_view_twitter { struct cmc_view v; struct sho_post *repost; struct { @@ -68,31 +73,45 @@ struct cmc_twitter_view { } stats; }; +struct cmc_view_page { + u32 num; + bool processed; + array(struct cmc_view *) views; +}; + +struct cmc_search { + struct camu_search *search; + bool find_anchor; + s32 anchor; + s32 prev_anchor; + u32 count; + s32 selected; + s32 prev_selected; + u32 max_page_num; + u32 min_page_num; + s32 end_of_page; + array(struct cmc_view_page *) pages; +}; + +struct cmc_tui { + struct notcurses *nc; + u32 cols; + u32 rows; + struct ncplane *p; + struct aki_poll input; + u32 req_index; + array(struct cmc_search *) searches; + struct cmc_search *search; +}; + struct cmc { - u8 mode; + u8 type; + u8 action; struct aki_event_loop loop; struct camu_client client; - struct camu_resource_client resource_client; + struct cmc_tui tui; u32 pending_requests; - struct notcurses *nc; - struct aki_poll input; - u32 rows; - u32 cols; - struct { - str provider; - str query; - s32 id; - struct camu_search *search; - } search; - struct { - struct ncplane *p; - array(struct cmc_view *) views; - u32 offset; - u32 roffset; - u32 prev; - u32 page; - u32 selected; - } ui; + struct camu_resource_client resource_client; array(struct cmc_view_resource *) resource_cache; }; @@ -120,6 +139,49 @@ nccell_release(view->v.container, &hl); nccell_release(view->v.container, &vl); //ncplane_double_box(view->v.container, NCSTYLE_NONE, color, rows, width, 0); */ +/* +static u32 calc_height_for_display(struct ncvgeom *geom, u32 width) +{ + f32 ratio = geom->pixx / (f32)geom->pixy; + if (ratio >= 1.f) { + return ceil((width / ratio) / 2.f) - 2; + } else { + return ceil((width / ratio) / 2.f) - 2; + } +} + +static void resource_center_zoom(struct cmc_view_resource *resource, s32 width, s32 height) +{ + struct ncvgeom *geom = &resource->geom; + struct ncvisual_options *vopts = &resource->vopts; + + f32 ratio = geom->pixx / (f32)geom->pixy; + f32 preview_ratio = width / (f32)height; + f32 diff; + + f32 x0 = 0.f; + f32 y0 = 0.f; + f32 x1 = geom->pixx; + f32 y1 = geom->pixy; + + // Not properly tested. + if (preview_ratio > ratio) { + diff = (geom->pixy - (geom->pixy / (preview_ratio * ratio))) / 2.f; + y0 += diff; + y1 -= diff; + } else { + diff = (geom->pixx - (geom->pixx / (preview_ratio / ratio))) / 2.f; + x0 += diff; + x1 -= diff; + } + + vopts->begx = x0; + vopts->begy = y0; + vopts->lenx = x1 - x0; + vopts->leny = y1 - y0; +} +*/ + static u32 count_wrapped_lines(wstr *w, u32 width) { u32 counter = 0, wraps = 1; @@ -179,60 +241,7 @@ static void number_to_shorthand(s64 number, char *out) #define PROFILE_SIZE 5 #define PREVIEW_OFFSET_WIDTH 26 -/* -static u32 calc_height_for_display(struct ncvgeom *geom, u32 width) -{ - f32 ratio = geom->pixx / (f32)geom->pixy; - if (ratio >= 1.f) { - return ceil((width / ratio) / 2.f) - 2; - } else { - return ceil((width / ratio) / 2.f) - 2; - } -} -*/ - -static void resource_center_zoom(struct cmc_view_resource *resource, s32 width, s32 height) -{ - struct ncvgeom *geom = &resource->geom; - struct ncvisual_options *vopts = &resource->vopts; - - f32 ratio = geom->pixx / (f32)geom->pixy; - f32 preview_ratio = width / (f32)height; - f32 diff; - - f32 x0 = 0.f; - f32 y0 = 0.f; - f32 x1 = geom->pixx; - f32 y1 = geom->pixy; - - // Not properly tested. - if (preview_ratio > ratio) { - diff = (geom->pixy - (geom->pixy / (preview_ratio * ratio))) / 2.f; - y0 += diff; - y1 -= diff; - } else { - diff = (geom->pixx - (geom->pixx / (preview_ratio / ratio))) / 2.f; - x0 += diff; - x1 -= diff; - } - - vopts->begx = x0; - vopts->begy = y0; - vopts->lenx = x1 - x0; - vopts->leny = y1 - y0; -} - -static void get_post_and_maybe_repost(struct cmc *c, str *unique_id, struct sho_post **post, struct sho_post **repost) -{ - *post = sho_post_cache_get(&c->client.cache, unique_id); - *repost = NULL; - if ((*post)->type == SHOKI_POST_REPOST) { - *repost = *post; - *post = sho_post_cache_get(&c->client.cache, &(*post)->post.unique_id); - } -} - -static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) +static u32 layout_tweet(struct cmc_tui *tui, struct cmc_view_twitter *view, u32 width) { struct sho_post *post = view->v.post; struct sho_post *repost = view->repost; @@ -241,36 +250,24 @@ static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) view->text.width = width - TEXT_OFFSET; view->text.height = 1 + count_wrapped_lines(&post->text, view->text.width); - if (repost) { - view->text.y = 1; - } else { - view->text.y = 0; - } + view->text.y = repost ? 1 : 0; view->text.height += view->text.y; rows += view->text.height; - view->v.has_visual_data = true; - - for (u32 i = 0; i < 4; i++) { - if (view->preview.resources[i] && !view->preview.resources[i]->v) { - view->v.has_visual_data = false; - break; - } + if (view->preview.mode == CMC_VIEW_NONE) { + return rows; } - if (view->preview.mode == CMC_VIEW_NONE || !view->v.has_visual_data) return rows; - - view->preview.width = view->text.width; - u32 partition_width = view->preview.width / 2; + view->preview.width = view->text.width - 1; view->preview.x = TEXT_OFFSET + 1; - view->preview.height = 0; + u32 partition_width = view->preview.width / 2; struct ncplane_options opts = { 0 }; opts.cols = view->preview.width; - opts.rows = ncplane_dim_y(notcurses_stdplane(c->nc)) / 3; + opts.rows = ncplane_dim_y(notcurses_stdplane(tui->nc)) / 3; opts.x = 0; opts.y = 0; - struct ncplane *inter = ncplane_create(notcurses_stdplane(c->nc), &opts); + struct ncplane *inter = ncplane_create(notcurses_stdplane(tui->nc), &opts); u32 left = 0, right = 0; struct cmc_view_resource *resource; @@ -278,8 +275,9 @@ static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) case CMC_VIEW_SINGLE: { resource = view->preview.resources[0]; resource->vopts.n = inter; - ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); - left += resource->geom.rcelly + 1; + ncvisual_geom(tui->nc, resource->v, &resource->vopts, &resource->geom); + // rcellx == width case very untested. + left += resource->geom.rcelly + (s32)((resource->geom.rcellx != view->preview.width) ? 1 : -1); break; } case CMC_VIEW_PARTITION: { @@ -287,93 +285,92 @@ static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) resource = view->preview.resources[0]; resource->vopts.n = inter; - ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + ncvisual_geom(tui->nc, resource->v, &resource->vopts, &resource->geom); left += resource->geom.rcelly + 1; resource = view->preview.resources[1]; resource->vopts.n = inter; - ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + ncvisual_geom(tui->nc, resource->v, &resource->vopts, &resource->geom); right += resource->geom.rcelly + 1; if (view->preview.resources[2]) { resource = view->preview.resources[2]; resource->vopts.n = inter; - ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + ncvisual_geom(tui->nc, resource->v, &resource->vopts, &resource->geom); left += resource->geom.rcelly + 1; } if (view->preview.resources[3]) { resource = view->preview.resources[3]; resource->vopts.n = inter; - ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + ncvisual_geom(tui->nc, resource->v, &resource->vopts, &resource->geom); right += resource->geom.rcelly + 1; } break; } } - view->preview.height = AL_MAX(right, left); ncplane_destroy(inter); + view->preview.height = AL_MAX(right, left); + return rows + view->preview.height; } static char format_buf[32]; -static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncplane *container, u32 rows, u32 width) +static void draw_tweet(struct cmc_tui *tui, struct cmc_view_twitter *view, struct ncplane *container, u32 rows, u32 width) { struct sho_post *post = view->v.post; struct sho_post *repost = view->repost; ncplane_resize_simple(container, rows, width); - if (view->v.has_visual_data) { - u32 partition_width = view->preview.width / 2; - struct cmc_view_resource *resource; - switch (view->preview.mode) { - case CMC_VIEW_NONE: - break; - case CMC_VIEW_SINGLE: { - resource = view->preview.resources[0]; - ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, view->preview.width); - ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x); - resource->vopts.n = view->preview.p[0]; - ncvisual_blit(c->nc, resource->v, &resource->vopts); - break; - } - case CMC_VIEW_PARTITION: { - resource = view->preview.resources[0]; - resource->vopts.n = view->preview.p[0]; - ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, partition_width); - ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x); - ncvisual_blit(c->nc, resource->v, &resource->vopts); - - resource = view->preview.resources[1]; - ncplane_resize_simple(view->preview.p[1], resource->geom.rcelly + 1, partition_width); - ncplane_move_yx(view->preview.p[1], view->text.height, (view->preview.x + view->preview.resources[0]->geom.rcellx) + 2); - resource->vopts.n = view->preview.p[1]; - ncvisual_blit(c->nc, resource->v, &resource->vopts); - - if (view->preview.resources[2]) { - resource = view->preview.resources[2]; - ncplane_resize_simple(view->preview.p[2], resource->geom.rcelly + 1, partition_width); - ncplane_move_yx(view->preview.p[2], view->preview.resources[0]->geom.rcelly + view->text.height + 2, view->preview.x); - resource->vopts.n = view->preview.p[2]; - ncvisual_blit(c->nc, resource->v, &resource->vopts); - } + u32 partition_width = view->preview.width / 2; + struct cmc_view_resource *resource; + switch (view->preview.mode) { + case CMC_VIEW_NONE: + break; + case CMC_VIEW_SINGLE: { + resource = view->preview.resources[0]; + ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, view->preview.width); + ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x); + resource->vopts.n = view->preview.p[0]; + ncvisual_blit(tui->nc, resource->v, &resource->vopts); + break; + } + case CMC_VIEW_PARTITION: { + resource = view->preview.resources[0]; + resource->vopts.n = view->preview.p[0]; + ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x); + ncvisual_blit(tui->nc, resource->v, &resource->vopts); - if (view->preview.resources[3]) { - resource = view->preview.resources[3]; - ncplane_resize_simple(view->preview.p[3], resource->geom.rcelly + 1, partition_width); - ncplane_move_yx(view->preview.p[3], view->preview.resources[2]->geom.rcelly + view->text.height + 2, (view->preview.x + view->preview.resources[2]->geom.rcellx) + 2); - resource->vopts.n = view->preview.p[3]; - ncvisual_blit(c->nc, resource->v, &resource->vopts); - } + resource = view->preview.resources[1]; + ncplane_resize_simple(view->preview.p[1], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[1], view->text.height, (view->preview.x + view->preview.resources[0]->geom.rcellx) + 3); + resource->vopts.n = view->preview.p[1]; + ncvisual_blit(tui->nc, resource->v, &resource->vopts); - break; + if (view->preview.resources[2]) { + resource = view->preview.resources[2]; + ncplane_resize_simple(view->preview.p[2], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[2], view->preview.resources[0]->geom.rcelly + view->text.height + 2, view->preview.x); + resource->vopts.n = view->preview.p[2]; + ncvisual_blit(tui->nc, resource->v, &resource->vopts); } + + if (view->preview.resources[3]) { + resource = view->preview.resources[3]; + ncplane_resize_simple(view->preview.p[3], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[3], view->preview.resources[2]->geom.rcelly + view->text.height + 2, (view->preview.x + view->preview.resources[2]->geom.rcellx) + 3); + resource->vopts.n = view->preview.p[3]; + ncvisual_blit(tui->nc, resource->v, &resource->vopts); } + + break; + } } ncplane_resize_simple(view->text.p, view->text.height, width); @@ -426,11 +423,6 @@ static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncpl if (post->comments.set) { const wchar_t *comment = L"🗩"; - //const wchar_t *comment = L"[]"; - //const wchar_t *comment = L"。○"; - //const wchar_t *comment = L"❑"; - //const wchar_t *comment = L"。ロ"; - //const wchar_t *comment = L".❍"; ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC); ncplane_putwstr_yx(view->stats.p, 0, offset, comment); ncplane_set_styles(view->stats.p, NCSTYLE_NONE); @@ -450,12 +442,13 @@ static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncpl offset += part; if (post->likes.set) { - const wchar_t *like = L"❤"; + //const wchar_t *like = L"❤"; + const wchar_t *like = L"♥"; ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC); ncplane_putwstr_yx(view->stats.p, 0, offset, like); ncplane_set_styles(view->stats.p, NCSTYLE_NONE); number_to_shorthand(post->likes.i, format_buf); - ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf); + ncplane_putstr_yx(view->stats.p, 0, offset + 2, format_buf); } if (view->profile.resource && view->profile.resource->v) { @@ -463,66 +456,178 @@ static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncpl ncplane_resize_simple(view->profile.p, (PROFILE_SIZE + 1) / 2, PROFILE_SIZE); ncplane_move_yx(view->profile.p, view->text.y, PROFILE_SIZE - 2); resource->vopts.n = view->profile.p; - ncvisual_blit(c->nc, resource->v, &resource->vopts); + ncvisual_blit(tui->nc, resource->v, &resource->vopts); } } -static void layout_tweets(struct cmc *c) +static struct cmc_view_page *get_page_from_num(struct cmc_search *search, u32 num) { - struct cmc_twitter_view *view; - for (u32 i = 0; i < c->ui.views.size; i++) { - view = (struct cmc_twitter_view *)al_array_at(c->ui.views, i); - ncplane_erase(view->v.container); - ncplane_erase(view->text.p); - ncplane_erase(view->profile.p); - for (u32 i = 0; i < 4; i++) { - ncplane_erase(view->preview.p[i]); - } - ncplane_erase(view->stats.p); - ncplane_reparent_family(view->v.container, view->v.container); + struct cmc_view_page *page; + al_array_foreach(search->pages, i, page) { + if (page->num == num) return page; + } + return NULL; +} + +static struct cmc_view_page *get_page_containing_index(struct cmc_search *search, u32 index, u32 *offset) +{ + u32 count = 0, size = 0; + struct cmc_view_page *page = NULL; + for (u32 i = search->min_page_num; i <= search->max_page_num; i++) { + if (!(page = get_page_from_num(search, i))) continue; + size = page->views.size; + count += size; + if (count > index) break; } + if (offset) *offset = size - (count - index); + return page; +} + +static void request_page_internal(struct cmc *c, u32 num) +{ + c->pending_requests++; + camu_client_get_page(&c->client, c->tui.search->search, num); +} - u32 y = 0; - u32 width = ncplane_dim_x(c->ui.p); - u32 rows = 0; - u32 i = c->ui.offset; - c->ui.page = 0; - for (; i < c->ui.views.size; i++) { - view = (struct cmc_twitter_view *)al_array_at(c->ui.views, i); - rows = layout_tweet(c, view, width); - if (y + rows > ncplane_dim_y(c->ui.p)) break; - c->ui.page++; - draw_tweet(c, view, view->v.container, rows, width); - ncplane_reparent_family(view->v.container, c->ui.p); - ncplane_move_yx(view->v.container, y, 0); - if (i - c->ui.offset == c->ui.selected) { - wchar_t box = L'█'; - u32 i = view->repost ? 1 : 0; - for (; i < rows - 1; i++) { - ncplane_cursor_move_yx(view->v.container, i, 0); - ncplane_putwc(view->v.container, box); +static bool request_page_at_page_offset(struct cmc *c, struct cmc_search *search, u32 index, s32 offset) +{ + struct cmc_view_page *page = get_page_containing_index(search, index, NULL); + if (page) { + s32 req = (s32)page->num + offset; + if (req < 0) return false; + request_page_internal(c, req); + } + return true; +} + +static bool iterate_views_to_height(struct cmc_search *search, u32 height, u32 anchor, + u32 *count, void (*func)(void *, struct cmc_view_twitter *, u32), void *userdata) +{ + u32 y = 0, offset; + bool out_of_space = false; + struct cmc_view_page *page = get_page_containing_index(search, anchor, &offset); + al_assert(page); + struct cmc_view_twitter *view; + do { + for (; offset < page->views.size; offset++) { + view = (struct cmc_view_twitter *)al_array_at(page->views, offset); + if (y + view->v.rows > height) { + out_of_space = true; + break; } + if (func) func(userdata, view, y); + y += view->v.rows + 1; + (*count)++; } - y += rows + 1; + offset = 0; + } while (!out_of_space && (page = get_page_from_num(search, page->num + 1))); + return out_of_space; +} + +static void draw_tweet_func(void *userdata, struct cmc_view_twitter *view, u32 y) +{ + struct cmc_tui *tui = (struct cmc_tui *)userdata; + draw_tweet(tui, view, view->v.container, view->v.rows, tui->cols); + ncplane_reparent_family(view->v.container, tui->p); + ncplane_move_yx(view->v.container, y, 0); + view->v.y = y; +} + +static void draw_selection_line(struct cmc_view_twitter *view, u32 y, u32 rows) +{ + ncplane_move_yx(view->v.container, y, 0); + wchar_t box = L'█'; + u32 k = view->repost ? 1 : 0; + for (; k < rows - 1; k++) { + ncplane_cursor_move_yx(view->v.container, k, 0); + ncplane_putwc(view->v.container, box); } +} - if (!c->ui.page) c->ui.page = 1; +static struct cmc_view_twitter *get_view_by_visual_index(struct cmc_search *search, s32 index) +{ + u32 offset; + struct cmc_view_page *page = get_page_containing_index(search, search->anchor + index, &offset); + if (!page) return NULL; + return (struct cmc_view_twitter *)al_array_at(page->views, offset); +} - if (c->ui.roffset != c->ui.offset) { - notcurses_refresh(c->nc, NULL, NULL); +static void layout_tweets(struct cmc *c, struct cmc_tui *tui, struct cmc_search *search) +{ + struct cmc_view_page *page; + struct cmc_view_twitter *view; + al_array_foreach(search->pages, i, page) { + if (page->processed) continue; + for (u32 j = 0; j < page->views.size; j++) { + view = (struct cmc_view_twitter *)al_array_at(page->views, j); + view->v.rows = layout_tweet(tui, view, tui->cols); + } + page->processed = true; } - notcurses_render(c->nc); - c->ui.roffset = c->ui.offset; + if (search->find_anchor) { + s32 anchor = search->anchor; + if (anchor < 0) { + if (!request_page_at_page_offset(c, search, 0, -1)) { + search->anchor = 0; + search->selected = 0; + search->find_anchor = false; + } + return; + } + u32 count = 1, prev_count; + while (anchor >= 0) { + count = 0; + iterate_views_to_height(search, tui->rows, anchor, &count, NULL, NULL); + if ((s32)(anchor + count) - 1 < search->anchor) break; + anchor--; + if (anchor < 0 && request_page_at_page_offset(c, search, 0, -1)) { + return; + } + prev_count = count; + } + search->anchor = anchor + 1; + search->prev_selected = -1; + search->selected = prev_count - 1; + search->find_anchor = false; + } - if (c->pending_requests == 0 && c->ui.offset + c->ui.page >= c->ui.views.size) { - camu_client_more_results(&c->client, c->search.search); + bool out_of_space = true; + if (search->prev_anchor == -1 || search->anchor != search->prev_anchor) { + al_array_foreach(search->pages, i, page) { + for (u32 j = 0; j < page->views.size; j++) { + view = (struct cmc_view_twitter *)al_array_at(page->views, j); + ncplane_erase(view->v.container); + ncplane_reparent_family(view->v.container, view->v.container); + } + } + search->count = 0; + out_of_space = iterate_views_to_height(search, tui->rows, search->anchor, &search->count, draw_tweet_func, tui); + notcurses_refresh(tui->nc, NULL, NULL); + } else if (search->prev_selected >= 0) { + view = get_view_by_visual_index(search, search->prev_selected); + if (view) { + ncplane_erase(view->v.container); + } + } + view = get_view_by_visual_index(search, search->selected); + if (view) { + draw_selection_line(view, view->v.y, view->v.rows); + } + notcurses_render(tui->nc); + if (search->count > 0 && !out_of_space && search->end_of_page == -1) { + // Index is the last displayed view. + request_page_at_page_offset(c, search, (search->anchor + search->count) - 1, 1); + search->prev_anchor = -1; + } else { + search->prev_anchor = search->anchor; } } static void resource_client_callback(void *userdata, u16 id, struct aki_buffer *buffer) { struct cmc *c = (struct cmc *)userdata; + struct cmc_tui *tui = &c->tui; struct cmc_view_resource *resource; al_array_foreach(c->resource_cache, i, resource) { if (resource->id == id) { @@ -530,62 +635,87 @@ static void resource_client_callback(void *userdata, u16 id, struct aki_buffer * u8 *pixels = stbi_load_from_memory(aki_buffer_get_ptr(buffer, 0), buffer->size, &w, &h, &channels, 4); resource->v = ncvisual_from_rgba(pixels, h, w * 4, w); stbi_image_free(pixels); - ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + ncvisual_geom(c->tui.nc, resource->v, &resource->vopts, &resource->geom); break; } } - if (--c->pending_requests == 0) layout_tweets(c); + if (--c->pending_requests == 0) layout_tweets(c, tui, tui->search); +} + +static void add_selected_from_index(struct cmc *c, struct cmc_search *search, u32 index) +{ + struct cmc_view_twitter *view = get_view_by_visual_index(search, search->selected); + if (view && view->v.post->media.size > index) { + camu_client_add(&c->client, &view->v.post->unique_id, index); + camu_client_skip(&c->client, 1); + } +} + +static bool on_last_page(struct cmc_search *search) +{ + return search->end_of_page >= 0 && search->anchor + search->count > (u32)search->end_of_page; +} + +static bool handle_input_search(struct cmc *c, struct cmc_search *search, struct ncinput *input) +{ + if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false; + if (input->id == 'j') { + if (search->selected + 1 >= (s32)search->count) { + if (c->pending_requests != 0 || on_last_page(search)) return false; + search->anchor += search->count; + search->prev_selected = -1; + search->selected = 0; + } else { + search->prev_selected = search->selected; + search->selected++; + } + return true; + } else if (input->id == 'k') { + if (search->selected - 1 < 0) { + if (c->pending_requests != 0) return false; + search->anchor--; + search->find_anchor = true; + search->prev_selected = -1; + } else { + search->prev_selected = search->selected; + search->selected--; + } + return true; + } else if (input->id == NCKEY_RETURN) { + add_selected_from_index(c, search, 0); + } else if (input->id == '1') { + add_selected_from_index(c, search, 0); + } else if (input->id == '2') { + add_selected_from_index(c, search, 1); + } else if (input->id == '3') { + add_selected_from_index(c, search, 2); + } else if (input->id == '4') { + add_selected_from_index(c, search, 3); + } + return false; } static void input_poll_callback(void *userdata, s32 revents) { struct cmc *c = (struct cmc *)userdata; + struct cmc_tui *tui = &c->tui; + struct cmc_search *search = tui->search; (void)revents; struct ncinput input; u32 ret; - bool layout = false; + bool do_layout = false; do { - ret = notcurses_get_nblock(c->nc, &input); + ret = notcurses_get_nblock(c->tui.nc, &input); if (ret == (u32)-1 || ret == 0) break; if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) { - if (input.id == 'j') { - if (c->ui.selected + 1 >= c->ui.page) { - if (c->ui.offset + c->ui.page >= c->ui.views.size) { - return; - } - c->ui.offset += c->ui.page; - c->ui.prev = c->ui.page; - c->ui.page = 0; - c->ui.selected = 0; - } else { - c->ui.selected++; - } - layout = true; - } else if (input.id == 'k') { - if ((s32)c->ui.selected - 1 < 0) { - if (c->ui.offset == 0 || (s32)c->ui.offset - (s32)c->ui.prev < 0) { - return; - } - c->ui.offset -= c->ui.prev; - c->ui.selected = c->ui.prev - 1; - c->ui.prev = 0; - } else { - c->ui.selected--; - } - layout = true; - } else if (input.id == NCKEY_RETURN) { - if (c->ui.views.size > 0) { - u32 selected = c->ui.offset + c->ui.selected; - struct cmc_twitter_view *view = (struct cmc_twitter_view *)al_array_at(c->ui.views, selected); - camu_client_add(&c->client, &view->v.post->unique_id, 0); - } - } else if (input.id == 'q') { + if (input.id == 'q') { aki_event_loop_break(&c->loop); break; } + if (search) do_layout = handle_input_search(c, search, &input); } } while (1); - if (layout) layout_tweets(c); + if (do_layout && c->pending_requests == 0) layout_tweets(c, tui, search); } static struct cmc_view_resource *create_resource(struct cmc *c, str *unique_id, u32 index) @@ -593,9 +723,7 @@ static struct cmc_view_resource *create_resource(struct cmc *c, str *unique_id, struct cmc_view_resource *resource = al_alloc_object(struct cmc_view_resource); al_array_push(c->resource_cache, resource); resource->vopts.blitter = NCBLIT_PIXEL; - //resource->vopts.blitter = NCBLIT_2x1; - resource->vopts.scaling = NCSCALE_SCALE_HIRES; - //resource->vopts.scaling = NCSCALE_STRETCH; + resource->vopts.scaling = NCSCALE_SCALE; resource->id = al_rand_u16(); al_str_clone(&resource->unique_id, unique_id); resource->index = index; @@ -615,64 +743,150 @@ static struct cmc_view_resource *get_resource_from_cache(struct cmc *c, str *uni return create_resource(c, unique_id, index); } -static int resize_cb(struct ncplane *p) +static void get_post_and_maybe_repost(struct cmc *c, str *unique_id, struct sho_post **post, struct sho_post **repost) +{ + *post = sho_post_cache_get(&c->client.cache, unique_id); + *repost = NULL; + if ((*post)->type == SHOKI_POST_REPOST) { + *repost = *post; + *post = sho_post_cache_get(&c->client.cache, &(*post)->post.unique_id); + } +} + +static struct cmc_view_twitter *create_view_twitter(struct cmc *c, str *unique_id) { - struct cmc *c = (struct cmc *)ncplane_userptr(p); + struct cmc_view_twitter *view = al_alloc_object(struct cmc_view_twitter); + get_post_and_maybe_repost(c, unique_id, &view->v.post, &view->repost); + + struct ncplane_options default_options = { 0 }; + default_options.cols = 1; + default_options.rows = 1; + default_options.x = 0; + default_options.y = 0; + + view->v.container = ncpile_create(c->tui.nc, &default_options); + view->text.p = ncplane_create(view->v.container, &default_options); + view->profile.p = ncplane_create(view->v.container, &default_options); + for (u32 i = 0; i < 4; i++) { + view->preview.p[i] = ncplane_create(view->v.container, &default_options); + } + view->stats.p = ncplane_create(view->v.container, &default_options); + + struct sho_post *post = view->v.post; + view->profile.resource = get_resource_from_cache(c, &post->author.unique_id, 0); + struct sho_post_media *media; + al_array_foreach_ptr(post->media, i, media) { + (void)media; + view->preview.resources[i] = get_resource_from_cache(c, &post->unique_id, i); + } + + switch (post->media.size) { + case 0: + view->preview.mode = CMC_VIEW_NONE; + break; + case 1: + view->preview.mode = CMC_VIEW_SINGLE; + break; + default: + view->preview.mode = CMC_VIEW_PARTITION; + break; + } + + view->v.rows = 0; + view->v.y = 0; + + return view; +} + +static struct cmc_search *create_search(struct camu_search *s) +{ + struct cmc_search *search = al_alloc_object(struct cmc_search); + search->search = s; + search->find_anchor = false; + search->anchor = 0; + search->prev_anchor = -1; + search->selected = 0; + search->prev_selected = -1; + search->max_page_num = s->last_page; + search->min_page_num = s->last_page; + search->end_of_page = -1; + al_array_init(search->pages); + return search; +} + +static s32 resize_cb(struct ncplane *p) +{ + struct cmc_tui *tui = (struct cmc_tui *)ncplane_userptr(p); u32 width, height; - notcurses_stddim_yx(c->nc, &height, &width); + notcurses_stddim_yx(tui->nc, &height, &width); struct ncplane_options nopts = { 0 }; - nopts.rows = height - 1; - nopts.cols = width - 4; + tui->cols = nopts.cols = width - 4; + tui->rows = nopts.rows = height - 1; nopts.x = 3; nopts.y = 1; nopts.flags = 0; - struct ncplane *oldp = c->ui.p; - c->ui.p = ncplane_create(p, &nopts); - if (oldp) { - ncplane_destroy(oldp); - layout_tweets(c); - } + tui->p = ncplane_create(p, &nopts); + // TODO: Consider layout. return 0; } static void client_callback(void *userdata, u8 op, void *opaque) { struct cmc *c = (struct cmc *)userdata; + struct cmc_tui *tui = &c->tui; switch (op) { - case CAMU_CLIENT_STATUS_UPDATED: { - switch (c->mode) { - case CMC_LIST_SEARCHES: { - struct camu_search *search; - al_array_foreach(c->client.state.searches, i, search) { - al_printf("%i %.*s\n", search->id, AL_STR_PRINTF(&search->query)); + case CAMU_CLIENT_STATE_UPDATED: { + switch (c->action) { + case CMC_ACTION_OPEN: { + if (!(tui->nc = notcurses_init(NULL, stdin))) return; + aki_poll_init(&tui->input, input_poll_callback, c); + aki_poll_set(&tui->input, notcurses_inputready_fd(tui->nc), AKI_POLL_READ); + aki_poll_start(&tui->input, &c->loop); + al_array_init(tui->searches); + struct camu_search *s; + al_array_foreach(c->client.state.searches, i, s) { + al_array_push(tui->searches, create_search(s)); } - aki_event_loop_break(&c->loop); - break; - } - case CMC_LIST_LISTS: { - struct camu_list *list; - al_array_foreach(c->client.state.lists, i, list) { - al_printf("%.*s\n", AL_STR_PRINTF(&list->name)); + if (tui->searches.size > tui->req_index) { + tui->search = al_array_at(tui->searches, tui->req_index); + u32 last_page = tui->search->search->last_page; + request_page_internal(c, last_page); + } else { + tui->search = NULL; } - aki_event_loop_break(&c->loop); - break; - } - case CMC_CREATE_SEARCH: { - camu_client_create_search(&c->client, &c->search.provider, &c->search.query); - break; - } - case CMC_OPEN_SEARCH: { - if (!(c->nc = notcurses_init(NULL, stdin))) return; - if (c->client.state.searches.size == 0) return; - c->search.search = al_array_at(c->client.state.searches, 0); - struct ncplane *stdplane = notcurses_stdplane(c->nc); + struct ncplane *stdplane = notcurses_stdplane(tui->nc); ncplane_set_resizecb(stdplane, resize_cb); - ncplane_set_userptr(stdplane, c); + ncplane_set_userptr(stdplane, tui); resize_cb(stdplane); - camu_client_resume_search(&c->client, c->search.search); - aki_poll_init(&c->input, input_poll_callback, c); - aki_poll_set(&c->input, notcurses_inputready_fd(c->nc), AKI_POLL_READ); - aki_poll_start(&c->input, &c->loop); + break; + } + case CMC_ACTION_LIST: { + switch (c->type) { + case CMC_LIST: { + struct camu_list *list; + al_array_foreach(c->client.state.lists, i, list) { + al_printf("%.*s:\n", AL_STR_PRINTF(&list->name)); + str *unique_id; + al_array_foreach_ptr(list->entries, j, unique_id) { + if ((s32)j == list->current) { + al_printf(" > %.*s\n", AL_STR_PRINTF(unique_id)); + } else { + al_printf(" %.*s\n", AL_STR_PRINTF(unique_id)); + } + } + } + aki_event_loop_break(&c->loop); + break; + } + case CMC_SEARCH: { + struct camu_search *search; + al_array_foreach(c->client.state.searches, i, search) { + al_printf("%u %x %.*s\n", i, search->id, AL_STR_PRINTF(&search->query)); + } + aki_event_loop_break(&c->loop); + break; + } + } break; } } @@ -683,79 +897,77 @@ static void client_callback(void *userdata, u8 op, void *opaque) break; } case CAMU_CLIENT_RESULTS: { - struct camu_search_results *results = (struct camu_search_results *)opaque; - str *unique_id; - al_array_foreach_ptr(results->unique_ids, i, unique_id) { - struct cmc_twitter_view *view = al_alloc_object(struct cmc_twitter_view); - al_array_push(c->ui.views, (struct cmc_view *)view); - get_post_and_maybe_repost(c, unique_id, &view->v.post, &view->repost); + struct cmc_search *search = tui->search; // wrong + struct camu_result_list *results = (struct camu_result_list *)opaque; - struct ncplane_options default_options = { 0 }; - default_options.cols = 1; - default_options.rows = 1; - default_options.x = 0; - default_options.y = 0; - - view->v.container = ncpile_create(c->nc, &default_options); - view->text.p = ncplane_create(view->v.container, &default_options); - view->profile.p = ncplane_create(view->v.container, &default_options); - for (u32 i = 0; i < 4; i++) { - view->preview.p[i] = ncplane_create(view->v.container, &default_options); + struct cmc_view_page *page = al_alloc_object(struct cmc_view_page); + al_array_push(search->pages, page); + page->num = results->num; + page->processed = false; + if (page->num > search->max_page_num) search->max_page_num = page->num; + if (page->num < search->min_page_num) search->min_page_num = page->num; + if (search->find_anchor) { + search->anchor += results->unique_ids.size; + if (search->end_of_page >= 0) { + search->end_of_page += results->unique_ids.size; } - view->stats.p = ncplane_create(view->v.container, &default_options); - - u32 prev_pending = c->pending_requests; + } - struct sho_post *post = view->v.post; - view->profile.resource = get_resource_from_cache(c, &post->author.unique_id, 0); - struct sho_post_media *media; - al_array_foreach_ptr(post->media, i, media) { - (void)media; - view->preview.resources[i] = get_resource_from_cache(c, &post->unique_id, i); - } + str *unique_id; + struct cmc_view_twitter *view; + al_array_foreach_ptr(results->unique_ids, i, unique_id) { + view = create_view_twitter(c, unique_id); + al_array_push(page->views, (struct cmc_view *)view); + } - view->v.has_visual_data = prev_pending == c->pending_requests; + if (--c->pending_requests == 0) layout_tweets(c, tui, search); - if (post->media.size == 0) { - view->preview.mode = CMC_VIEW_NONE; - } else if (post->media.size == 1) { - view->preview.mode = CMC_VIEW_SINGLE; - } else { - view->preview.mode = CMC_VIEW_PARTITION; - } - } - if (c->pending_requests == 0) layout_tweets(c); break; } + case CAMU_CLIENT_END_OF_RESULTS: { + struct cmc_search *search = tui->search; // wrong + search->end_of_page = (search->anchor + search->count) - 1; + if (--c->pending_requests == 0) layout_tweets(c, tui, search); + break; + } + case CAMU_CLIENT_DISCONNECTED: + aki_event_loop_break(&c->loop); + break; } } +static struct cmc c = { 0 }; + s32 main(s32 argc, char *argv[]) { if (argc < 2 || !aki_common_init()) return EXIT_FAILURE; - struct cmc c = { 0 }; - - al_array_init(c.ui.views); - al_array_init(c.resource_cache); - c.pending_requests = 0; - c.ui.offset = 0; + str module; + str query; str *cmd = al_str_c(argv[1]); if (al_str_eq(cmd, al_str_c("search"))) { + c.type = CMC_SEARCH; if (argc == 2) { - c.mode = CMC_LIST_SEARCHES; + c.action = CMC_ACTION_LIST; } else { - c.mode = CMC_CREATE_SEARCH; - al_str_from(&c.search.provider, "twitter"); - al_str_from(&c.search.query, argv[2]); + c.action = CMC_ACTION_CREATE; + al_str_from(&module, "twitter"); + al_str_from(&query, argv[2]); } } else if (al_str_eq(cmd, al_str_c("open"))) { - c.mode = CMC_OPEN_SEARCH; + c.action = CMC_ACTION_OPEN; + if (argc > 2) { + c.tui.req_index = al_str_to_long(al_str_c(argv[2]), 10); + } else { + c.tui.req_index = 0; + } } else if (al_str_eq(cmd, al_str_c("list"))) { + c.type = CMC_LIST; if (argc == 2) { - c.mode = CMC_LIST_LISTS; + c.action = CMC_ACTION_LIST; } else { + c.action = CMC_ACTION_CREATE; } } else { return EXIT_FAILURE; @@ -764,18 +976,31 @@ s32 main(s32 argc, char *argv[]) aki_event_loop_init(&c.loop); camu_client_init(&c.client, &c.loop, client_callback, &c); - camu_client_login(&c.client, al_str_c("andrew"), TREE_SERVER_IP, TREE_PORT); + if (!camu_client_login(&c.client, al_str_c("andrew"), TREE_SERVER_IP, TREE_PORT)) { + goto err; + } - camu_resource_client_init(&c.resource_client, &c.loop, resource_client_callback, &c); - camu_resource_client_connect(&c.resource_client, TREE_SERVER_IP, TREE_RESOURCE_PORT); + if (c.action == CMC_ACTION_CREATE) { + if (c.type == CMC_SEARCH) { + camu_client_create_search(&c.client, &module, &query); + } + } + + if (c.action == CMC_ACTION_OPEN) { + c.pending_requests = 0; + camu_resource_client_init(&c.resource_client, &c.loop, resource_client_callback, &c); + camu_resource_client_connect(&c.resource_client, TREE_SERVER_IP, TREE_RESOURCE_PORT); + } aki_event_loop_run(&c.loop); - if (c.mode == CMC_OPEN_SEARCH) notcurses_stop(c.nc); + if (c.action == CMC_ACTION_OPEN && c.tui.nc) notcurses_stop(c.tui.nc); camu_client_close(&c.client); aki_common_close(); return EXIT_SUCCESS; +err: + return EXIT_FAILURE; } diff --git a/src/fruits/sink/sink.c b/src/fruits/sink/sink.c index d20f7c4..08d2886 100644 --- a/src/fruits/sink/sink.c +++ b/src/fruits/sink/sink.c @@ -106,8 +106,10 @@ static void screen_callback(void *userdata, u8 op, f64 float0) struct cmv *c = (struct cmv *)userdata; switch (op) { case CAMU_SCREEN_NEXT: + camu_sink_skip(&c->sink, 1); break; case CAMU_SCREEN_PREVIOUS: + camu_sink_skip(&c->sink, -1); break; case CAMU_SCREEN_TOGGLE_PAUSE: camu_sink_toggle_pause(&c->sink); @@ -189,6 +191,8 @@ s32 wmain(s32 argc, wchar_t **argv) aki_thread_join(&thread0); + camu_sink_free(&c.sink); + camu_mixer_close(&c.mixer); c.renderer->free(&c.renderer); camu_screen_close(&c.scr); diff --git a/src/libclient/client.c b/src/libclient/client.c index e42f662..71825e3 100644 --- a/src/libclient/client.c +++ b/src/libclient/client.c @@ -1,3 +1,7 @@ +#include <al/log.h> + +#include "../tree/common.h" + #include "client.h" static void identifed_callback(void *userdata, struct aki_packet *packet) @@ -19,18 +23,22 @@ static void connection_callback(void *userdata, struct aki_rpc_connection *conn) static void connection_closed_callback(void *userdata, struct aki_rpc_connection *conn) { - (void)userdata; + struct camu_client *client = (struct camu_client *)userdata; (void)conn; + client->callback(client->userdata, CAMU_CLIENT_DISCONNECTED, NULL); } static u8 packet_pool_callback(void *userdata, struct aki_packet *packet) { struct camu_client *client = (struct camu_client *)userdata; + while (!client->conn) { + aki_event_loop_run_once(client->loop); + } aki_rpc_connection_command(client->conn, packet, packet->userdata, client); return AKI_PACKET_POOL_NOP; } -static bool status_command_callback(void *userdata, struct aki_rpc_connection *conn, +static bool update_state_command_callback(void *userdata, struct aki_rpc_connection *conn, struct aki_packet *packet, struct aki_packet *rpacket) { struct camu_client *client = (struct camu_client *)userdata; @@ -43,6 +51,15 @@ static bool status_command_callback(void *userdata, struct aki_rpc_connection *c struct camu_list *list = al_alloc_object(struct camu_list); aki_packet_read_str(packet, &s); al_str_clone(&list->name, &s); + al_array_init(list->entries); + u32 entries = aki_packet_read_u32(packet); + for (u32 j = 0; j < entries; j++) { + aki_packet_read_str(packet, &s); + str unique_id; + al_str_clone(&unique_id, &s); + al_array_push(list->entries, unique_id); + } + list->current = aki_packet_read_s32(packet); al_array_push(client->state.lists, list); } @@ -51,20 +68,20 @@ static bool status_command_callback(void *userdata, struct aki_rpc_connection *c struct camu_search *search = al_alloc_object(struct camu_search); search->id = aki_packet_read_s32(packet); aki_packet_read_str(packet, &s); - al_str_clone(&search->provider, &s); + al_str_clone(&search->module, &s); aki_packet_read_str(packet, &s); al_str_clone(&search->query, &s); - search->page = aki_packet_read_s32(packet); + search->last_page = aki_packet_read_u32(packet); al_array_push(client->state.searches, search); } - client->callback(client->userdata, CAMU_CLIENT_STATUS_UPDATED, NULL); + client->callback(client->userdata, CAMU_CLIENT_STATE_UPDATED, NULL); return false; } static struct aki_rpc_command commands[] = { - { .op = TREE_CMD_STATUS, .callback = status_command_callback, .userdata = NULL } + { .op = TREE_CMD_UPDATE_STATE, .callback = update_state_command_callback, .userdata = NULL } }; bool camu_client_init(struct camu_client *client, struct aki_event_loop *loop, @@ -105,16 +122,16 @@ static void create_search_callback(void *userdata, struct aki_packet *packet) client->callback(client->userdata, CAMU_CLIENT_SEARCH_CREATED, &id); } -void camu_client_create_search(struct camu_client *client, str *provider, str *query) +void camu_client_create_search(struct camu_client *client, str *module, str *query) { - struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_SEARCH); - aki_packet_write_str(packet, provider); + struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_CREATE_SEARCH); + aki_packet_write_str(packet, module); aki_packet_write_str(packet, query); packet->userdata = create_search_callback; aki_packet_pool_submit(&client->pool, packet); } -static struct camu_search *search_from_id(struct camu_client *client, s32 id) +static struct camu_search *get_search_from_id(struct camu_client *client, s32 id) { struct camu_search *search; al_array_foreach(client->state.searches, i, search) { @@ -126,49 +143,55 @@ static struct camu_search *search_from_id(struct camu_client *client, s32 id) static void results_callback(void *userdata, struct aki_packet *packet) { struct camu_client *client = (struct camu_client *)userdata; + s32 ok = aki_packet_read_s32(packet); - if (ok != 0) return; + if (ok != 0) { + client->callback(client->userdata, CAMU_CLIENT_END_OF_RESULTS, NULL); + return; + } + s32 id = aki_packet_read_s32(packet); - struct camu_search *search = search_from_id(client, id); + struct camu_search *search = get_search_from_id(client, id); if (!search) return; - s32 page = aki_packet_read_s32(packet); - while (search->pages.size <= (u32)page) { - struct camu_search_results result; - result.page = page; - al_array_init(result.unique_ids); - al_array_push(search->pages, result); + + u32 page_num = aki_packet_read_u32(packet); + struct camu_result_list *list; + al_array_foreach(search->lists, i, list) { + if (list->num == page_num) { + al_log_warn("client", "Requested page was already loaded."); + return; + } } + + list = al_alloc_object(struct camu_result_list); + al_array_push(search->lists, list); + list->num = page_num; + al_array_init(list->unique_ids); + u32 size = aki_packet_read_u32(packet); for (u32 i = 0; i < size; i++) { struct sho_post post; aki_packet_read_sho_post(packet, &post); sho_post_cache_push(&client->cache, &post); } - struct camu_search_results *result = &al_array_at(search->pages, page); - str s, unique_id; + + str s; + str unique_id; size = aki_packet_read_u32(packet); for (u32 i = 0; i < size; i++) { aki_packet_read_str(packet, &s); al_str_clone(&unique_id, &s); - al_array_push(result->unique_ids, unique_id); + al_array_push(list->unique_ids, unique_id); } - client->callback(client->userdata, CAMU_CLIENT_RESULTS, result); -} -void camu_client_resume_search(struct camu_client *client, struct camu_search *search) -{ - struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_RESUME_SEARCH); - aki_packet_write_s32(packet, search->id); - aki_packet_write_s32(packet, search->page); - packet->userdata = results_callback; - aki_packet_pool_submit(&client->pool, packet); + client->callback(client->userdata, CAMU_CLIENT_RESULTS, list); } -void camu_client_more_results(struct camu_client *client, struct camu_search *search) +void camu_client_get_page(struct camu_client *client, struct camu_search *search, u32 num) { - struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_RESUME_SEARCH); + struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_GET_PAGE); aki_packet_write_s32(packet, search->id); - aki_packet_write_s32(packet, -1); + aki_packet_write_u32(packet, num); packet->userdata = results_callback; aki_packet_pool_submit(&client->pool, packet); } @@ -182,6 +205,14 @@ void camu_client_add(struct camu_client *client, str *unique_id, u32 index) aki_packet_pool_submit(&client->pool, packet); } +void camu_client_skip(struct camu_client *client, s32 n) +{ + struct aki_packet *packet = aki_rpc_get_packet(&client->client, TREE_CMD_SKIP); + aki_packet_write_s32(packet, n); + packet->userdata = NULL; + aki_packet_pool_submit(&client->pool, packet); +} + void camu_client_close(struct camu_client *client) { aki_packet_pool_free(&client->pool); diff --git a/src/libclient/client.h b/src/libclient/client.h index cdcaffe..2b2e093 100644 --- a/src/libclient/client.h +++ b/src/libclient/client.h @@ -6,31 +6,33 @@ #include <sho/post.h> #include <sho/post_cache.h> -#include "../tree/common.h" - enum { CAMU_CLIENT_CONNECTED = 0, - CAMU_CLIENT_STATUS_UPDATED, + CAMU_CLIENT_STATE_UPDATED, CAMU_CLIENT_SEARCH_CREATED, - CAMU_CLIENT_RESULTS + CAMU_CLIENT_RESULTS, + CAMU_CLIENT_END_OF_RESULTS, + CAMU_CLIENT_DISCONNECTED }; struct camu_list { str name; + s32 current; array(str) entries; }; -struct camu_search_results { - s32 page; +struct camu_result_list { + u32 num; + s32 search_id; array(str) unique_ids; }; struct camu_search { s32 id; - str provider; + str module; str query; - s32 page; - array(struct camu_search_results) pages; + u32 last_page; + array(struct camu_result_list *) lists; }; struct camu_client { @@ -51,8 +53,8 @@ struct camu_client { bool camu_client_init(struct camu_client *client, struct aki_event_loop *loop, void (*callback)(void *, u8, void *), void *userdata); bool camu_client_login(struct camu_client *client, str *username, str *addr, s32 port); -void camu_client_create_search(struct camu_client *client, str *provider, str *query); -void camu_client_resume_search(struct camu_client *client, struct camu_search *search); -void camu_client_more_results(struct camu_client *client, struct camu_search *search); +void camu_client_create_search(struct camu_client *client, str *module, str *query); +void camu_client_get_page(struct camu_client *client, struct camu_search *search, u32 num); void camu_client_add(struct camu_client *client, str *unique_id, u32 index); +void camu_client_skip(struct camu_client *client, s32 n); void camu_client_close(struct camu_client *client); diff --git a/src/libclient/commands.h b/src/libclient/commands.h deleted file mode 100644 index 6f70f09..0000000 --- a/src/libclient/commands.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/src/libclient/resource_client.c b/src/libclient/resource_client.c index 77c19c7..1e07b0b 100644 --- a/src/libclient/resource_client.c +++ b/src/libclient/resource_client.c @@ -1,7 +1,5 @@ #include <al/random.h> -#include "../tree/tree.h" - #include "resource_client.h" static void connection_callback(void *userdata, struct aki_packet_stream *stream) diff --git a/src/libsink/common.h b/src/libsink/common.h index 1c701c8..48e43e8 100644 --- a/src/libsink/common.h +++ b/src/libsink/common.h @@ -2,5 +2,6 @@ enum { CAMU_SINK_CMD_BUFFER = 0, - CAMU_SINK_CMD_SET + CAMU_SINK_CMD_SET, + CAMU_SINK_CMD_QUEUE, }; diff --git a/src/libsink/sink.c b/src/libsink/sink.c index 3eecf5f..134be31 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -1,9 +1,12 @@ #include <al/log.h> #include "../tree/common.h" + #include "../bimu/common.h" #include "../bimu/handler.h" +#include "../buffer/common.h" + #include "sink.h" #include "common.h" @@ -31,15 +34,18 @@ enum { START, STOP, TOGGLE_PAUSE, + SKIP, RESEEK, SEEK, SET_BUFFERED, // Currently set entry is buffered. CLOSE, // Internal. CORK, - UNCORK + UNCORK, }; +#define BIMU_DELAY_IGNORE 0 + #define ENTRY_AUDIO_BUFFER_HELD(entry) \ (al_atomic_bool_load(&(entry)->audio.buf.ref, AL_ATOMIC_RELAXED)) #ifdef CAMU_SINK_NO_VIDEO @@ -55,6 +61,36 @@ enum { #define ENTRY_AUDIO_EMPTY(entry) \ (entry->audio.state == BUFFER_INIT || entry->audio.state == BUFFER_QUEUED) +static void remove_entry_buffers(struct camu_sink *sink, struct camu_sink_entry *entry) +{ +#ifndef CAMU_SINK_NO_VIDEO + if (entry->video.state == BUFFER_ADDED) { + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); + entry->video.state = BUFFER_SET_OR_BUFFERED; + } +#endif + if (entry->audio.state == BUFFER_ADDED) { + sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); + entry->audio.state = BUFFER_SET_OR_BUFFERED; + } +} + +static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry); +#ifndef CAMU_SINK_NO_VIDEO +static void add_video_if_set_and_buffered(struct camu_sink_entry *entry); +#endif + +static void swap_buffers_internal(struct camu_sink *sink) +{ + remove_entry_buffers(sink, sink->current); + sink->current = sink->queued; + sink->queued = NULL; + add_audio_if_set_and_buffered(sink->current); +#ifndef CAMU_SINK_NO_VIDEO + add_video_if_set_and_buffered(sink->current); +#endif +} + static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) { switch (cmd->op) { @@ -122,6 +158,12 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) } break; } + case SKIP: { + struct aki_packet *packet = aki_rpc_get_packet(&sink->client, TREE_CMD_SKIP); + aki_packet_write_s32(packet, cmd->value.i); + aki_rpc_connection_command(sink->conn, packet, NULL, NULL); + break; + } case RESEEK: { struct camu_sink_entry *entry = (struct camu_sink_entry *)cmd->opaque; bmu_client_reseek(&entry->client); @@ -146,6 +188,7 @@ static void handle_sink_cmd(struct camu_sink *sink, struct camu_sink_cmd *cmd) // break; // } case CLOSE: { + aki_signal_stop(&sink->signal); sink->callback(sink->userdata, CAMU_SINK_EXIT, 0, NULL); return; } @@ -198,11 +241,16 @@ static void queue_cmd(struct camu_sink *sink, struct camu_sink_cmd cmd) aki_signal_send(&sink->signal); } -static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) +void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) { u8 state = entry->audio.state; if (state == BUFFER_SET_OR_BUFFERED) { + // TODO: should_resume is not robust. +#ifndef CAMU_SINK_NO_VIDEO bool should_resume = ENTRY_VIDEO_EMPTY(entry) || entry->video.state == BUFFER_ADDED; +#else + bool should_resume = true; +#endif if (should_resume && !camu_clock_calc_tick(&entry->clock)) { queue_cmd(entry->sink, (struct camu_sink_cmd){ .op = RESEEK, @@ -230,6 +278,40 @@ static void add_audio_if_set_and_buffered(struct camu_sink_entry *entry) entry->audio.state = state; } +#ifndef CAMU_SINK_NO_VIDEO +void add_video_if_set_and_buffered(struct camu_sink_entry *entry) +{ + u8 state = entry->video.state; + if (state == BUFFER_SET_OR_BUFFERED) { + bool should_resume = ENTRY_AUDIO_EMPTY(entry) || entry->audio.state == BUFFER_ADDED; + if (should_resume && !camu_clock_calc_tick(&entry->clock)) { + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = RESEEK, + .opaque = entry + }); + return; + } + queue_cmd(entry->sink, (struct camu_sink_cmd){ + .op = START, + .value.i = CAMU_SINK_VIDEO + }); + if (should_resume) { + camu_clock_resume(&entry->clock); + entry->state = ENTRY_BUFFERED; + } + entry->sink->callback(entry->sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); + //queue_cmd(entry->sink, (struct camu_sink_cmd){ + // .op = SET_BUFFERED, + // .value.i = CAMU_SINK_VIDEO + //}); + state = BUFFER_ADDED; + } else if (state == BUFFER_CONFIGURED) { + state = BUFFER_SET_OR_BUFFERED; + } + entry->video.state = state; +} +#endif + static void audio_buffer_callback(void *userdata, u8 op) { struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; @@ -260,15 +342,16 @@ static void audio_buffer_callback(void *userdata, u8 op) }); break; case CAMU_BUFFER_EOF: { - //aki_mutex_lock(&entry->sink->mutex); - //u8 ret = entry->sink->callback(entry->sink->userdata, CAMU_SINK_SWAP_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); - //entry->audio.state = BUFFER_SET_OR_BUFFERED; - //aki_mutex_unlock(&entry->sink->mutex); - //if (ret != CAMU_SINK_BUFFERS_SWAPPED) { - if (1) { - // TODO: Can cause popping. Should run a timer - // and if any action would queue_cmd(AUDIO_START) before the - // the timer, don't queue the stop. + struct camu_sink *sink = entry->sink; + bool swapped = false; + aki_mutex_lock(&sink->mutex); + if (sink->queued) { + swap_buffers_internal(sink); + swapped = true; + al_log_info("sink", "Audio buffers swapped (gapless)."); + } + aki_mutex_unlock(&sink->mutex); + if (!swapped) { queue_cmd(entry->sink, (struct camu_sink_cmd){ .op = STOP, .value.i = CAMU_SINK_AUDIO @@ -286,38 +369,6 @@ static void audio_buffer_callback(void *userdata, u8 op) } #ifndef CAMU_SINK_NO_VIDEO -static void add_video_if_set_and_buffered(struct camu_sink_entry *entry) -{ - u8 state = entry->video.state; - if (state == BUFFER_SET_OR_BUFFERED) { - bool should_resume = ENTRY_AUDIO_EMPTY(entry) || entry->audio.state == BUFFER_ADDED; - if (should_resume && !camu_clock_calc_tick(&entry->clock)) { - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = RESEEK, - .opaque = entry - }); - return; - } - queue_cmd(entry->sink, (struct camu_sink_cmd){ - .op = START, - .value.i = CAMU_SINK_VIDEO - }); - if (should_resume) { - camu_clock_resume(&entry->clock); - entry->state = ENTRY_BUFFERED; - } - entry->sink->callback(entry->sink->userdata, CAMU_SINK_ADD_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); - //queue_cmd(entry->sink, (struct camu_sink_cmd){ - // .op = SET_BUFFERED, - // .value.i = CAMU_SINK_VIDEO - //}); - state = BUFFER_ADDED; - } else if (state == BUFFER_CONFIGURED) { - state = BUFFER_SET_OR_BUFFERED; - } - entry->video.state = state; -} - static void video_buffer_callback(void *userdata, u8 op) { struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; @@ -388,7 +439,7 @@ static void client_callback(void *userdata, u8 op, struct bmu_vcr_stream *stream case BIMU_CLIENT_SET: { u64 ts = aki_get_timestamp(); struct bmu_seek_req *req = (struct bmu_seek_req *)opaque; - req->delay = 0; // Ignore delay. + req->delay = BIMU_DELAY_IGNORE; req->ts -= req->delay; bool late = ts <= req->ts; if (late) {} @@ -502,6 +553,7 @@ bool camu_sink_init(struct camu_sink *sink, struct aki_event_loop *loop, aki_signal_start(&sink->signal, sink->loop); camu_queue_init(sink->queue); aki_mutex_init(&sink->mutex); + sink->queued = NULL; sink->current = NULL; al_array_init(sink->entries); sink->audio.mixer = mixer; @@ -526,27 +578,19 @@ static void buffer_timer_callback(void *userdata, struct aki_timer *timer) { struct camu_sink_entry *entry = (struct camu_sink_entry *)userdata; (void)timer; - aki_mutex_lock(&entry->sink->mutex); - aki_mutex_unlock(&entry->sink->mutex); + //aki_mutex_lock(&entry->sink->mutex); + //aki_mutex_unlock(&entry->sink->mutex); aki_timer_stop(&entry->timer); } -static bool buffer_command_callback(void *userdata, struct aki_rpc_connection *conn, - struct aki_packet *packet, struct aki_packet *rpacket) +static struct camu_sink_entry *ensure_entry_buffered_internal(struct camu_sink *sink, str *addr, s32 port, u32 node_id) { - struct camu_sink *sink = (struct camu_sink *)userdata; - (void)conn; - (void)rpacket; - - str addr; - aki_packet_read_str(packet, &addr); - s32 port = aki_packet_read_s32(packet); - u16 node_id = aki_packet_read_u16(packet); + struct camu_sink_entry *entry = entry_from_node_id(sink, node_id); + if (entry) return entry; - struct camu_sink_entry *entry = al_alloc_object(struct camu_sink_entry); - entry->sink = sink; + entry = al_alloc_object(struct camu_sink_entry); al_array_push(sink->entries, entry); - + entry->sink = sink; entry->state = ENTRY_LOADED; entry->audio.state = BUFFER_INIT; @@ -567,26 +611,32 @@ static bool buffer_command_callback(void *userdata, struct aki_rpc_connection *c entry->client.callback = client_callback; entry->client.userdata = entry; - bmu_client_connect(&entry->client, sink->loop, &addr, port, node_id); + bmu_client_connect(&entry->client, sink->loop, addr, port, node_id); + + return entry; +} + +static bool buffer_command_callback(void *userdata, struct aki_rpc_connection *conn, + struct aki_packet *packet, struct aki_packet *rpacket) +{ + struct camu_sink *sink = (struct camu_sink *)userdata; + (void)conn; + (void)rpacket; + + str addr; + aki_packet_read_str(packet, &addr); + s32 port = aki_packet_read_s32(packet); + u16 node_id = aki_packet_read_u16(packet); + + aki_mutex_lock(&sink->mutex); + ensure_entry_buffered_internal(sink, &addr, port, node_id); + aki_mutex_unlock(&sink->mutex); aki_packet_free(packet); return false; } -static void remove_entry_buffers(struct camu_sink *sink, struct camu_sink_entry *entry) -{ -#ifndef CAMU_SINK_NO_VIDEO - if (entry->video.state == BUFFER_ADDED) { - sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_VIDEO, &entry->video.buf); - entry->video.state = BUFFER_SET_OR_BUFFERED; - } -#endif - if (entry->audio.state == BUFFER_ADDED) { - sink->callback(sink->userdata, CAMU_SINK_REMOVE_BUFFER, CAMU_SINK_AUDIO, &entry->audio.buf); - entry->audio.state = BUFFER_SET_OR_BUFFERED; - } -} static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn, struct aki_packet *packet, struct aki_packet *rpacket) @@ -595,12 +645,17 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn (void)conn; (void)rpacket; + str addr; + aki_packet_read_str(packet, &addr); + s32 port = aki_packet_read_s32(packet); u16 node_id = aki_packet_read_u16(packet); + aki_mutex_lock(&sink->mutex); - struct camu_sink_entry *entry = entry_from_node_id(sink, node_id); - if (!entry) goto out; + struct camu_sink_entry *entry = ensure_entry_buffered_internal(sink, &addr, port, node_id); + if (entry == sink->current) goto out; struct camu_sink_entry *previous = sink->current; sink->current = entry; + if (sink->queued) sink->queued = NULL; if (entry->audio.state == BUFFER_INIT) { entry->audio.state = BUFFER_QUEUED; } else { @@ -617,16 +672,35 @@ static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn camu_clock_pause(&previous->clock); remove_entry_buffers(sink, previous); } - out: aki_mutex_unlock(&sink->mutex); aki_packet_free(packet); return false; } +static bool queue_command_callback(void *userdata, struct aki_rpc_connection *conn, + struct aki_packet *packet, struct aki_packet *rpacket) +{ + struct camu_sink *sink = (struct camu_sink *)userdata; + (void)conn; + (void)rpacket; + + str addr; + aki_packet_read_str(packet, &addr); + s32 port = aki_packet_read_s32(packet); + u16 node_id = aki_packet_read_u16(packet); + + aki_mutex_lock(&sink->mutex); + sink->queued = ensure_entry_buffered_internal(sink, &addr, port, node_id); + aki_mutex_unlock(&sink->mutex); + + return false; +} + static struct aki_rpc_command commands[] = { { .op = CAMU_SINK_CMD_BUFFER, .callback = buffer_command_callback, .userdata = NULL }, - { .op = CAMU_SINK_CMD_SET, .callback = set_command_callback, .userdata = NULL } + { .op = CAMU_SINK_CMD_SET, .callback = set_command_callback, .userdata = NULL }, + { .op = CAMU_SINK_CMD_QUEUE, .callback = queue_command_callback, .userdata = NULL } }; static void connection_callback(void *userdata, struct aki_rpc_connection *conn) @@ -640,7 +714,8 @@ static void connection_callback(void *userdata, struct aki_rpc_connection *conn) static void connection_closed_callback(void *userdata, struct aki_rpc_connection *conn) { - (void)userdata; + struct camu_sink *sink = (struct camu_sink *)userdata; + (void)sink; (void)conn; } @@ -685,8 +760,10 @@ void camu_sink_seek(struct camu_sink *sink, f64 precent) void camu_sink_skip(struct camu_sink *sink, s32 n) { - (void)sink; - (void)n; + queue_cmd(sink, (struct camu_sink_cmd){ + .op = SKIP, + .value.i = n + }); } struct camu_sink_entry *camu_sink_get_current(struct camu_sink *sink) @@ -719,15 +796,21 @@ void camu_sink_stop(struct camu_sink *sink) void camu_sink_close(struct camu_sink *sink) { + // TODO: Make sure no commands can come in after this. + aki_rpc_disconnect(&sink->client); aki_mutex_lock(&sink->mutex); struct camu_sink_entry *entry; al_array_foreach(sink->entries, i, entry) { bmu_client_close(&entry->client); + al_array_remove_at_iter(sink->entries, i); } aki_mutex_unlock(&sink->mutex); +} + +void camu_sink_free(struct camu_sink *sink) +{ al_array_free(sink->entries); - aki_rpc_disconnect(&sink->client); - aki_signal_stop(&sink->signal); + aki_rpc_free(&sink->client); camu_queue_free(sink->queue); aki_mutex_destroy(&sink->mutex); } diff --git a/src/libsink/sink.h b/src/libsink/sink.h index e3550bb..40d20d4 100644 --- a/src/libsink/sink.h +++ b/src/libsink/sink.h @@ -73,6 +73,7 @@ struct camu_sink { struct aki_signal signal; queue(struct camu_sink_cmd) queue; struct aki_mutex mutex; + struct camu_sink_entry *queued; struct camu_sink_entry *current; array(struct camu_sink_entry *) entries; struct { @@ -103,3 +104,4 @@ struct camu_sink_entry *camu_sink_get_current(struct camu_sink *sink); void camu_sink_return_current(struct camu_sink *sink); void camu_sink_stop(struct camu_sink *sink); void camu_sink_close(struct camu_sink *sink); +void camu_sink_free(struct camu_sink *sink); diff --git a/src/screen/screen.c b/src/screen/screen.c index 0baaef3..938e01d 100644 --- a/src/screen/screen.c +++ b/src/screen/screen.c @@ -129,6 +129,7 @@ static bool key_callback(void *userdata, u8 state, u8 button) struct camu_screen *scr = (struct camu_screen *)userdata; switch (state) { case SEKIHI_BUTTON_PRESSED: + //al_printf("%x\n", button); switch (button) { case 0x10: case 0x18: @@ -137,11 +138,13 @@ static bool key_callback(void *userdata, u8 state, u8 button) break; case 0x6a: case 0x31: + case 0x4d: case 'n': scr->callback(scr->userdata, CAMU_SCREEN_NEXT, 0.0); break; case 0x69: case 0x30: + case 0x4b: case 'b': scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, 0.0); break; diff --git a/src/tree/common.h b/src/tree/common.h index 59c713d..de6258b 100644 --- a/src/tree/common.h +++ b/src/tree/common.h @@ -6,8 +6,8 @@ #define TREE_RESOURCE_PORT 14357 #define TREE_STREAM_PORT 14358 -//#define TREE_SERVER_IP al_str_c("127.0.0.1") -#define TREE_SERVER_IP al_str_c("108.52.160.112") +#define TREE_SERVER_IP al_str_c("127.0.0.1") +//#define TREE_SERVER_IP al_str_c("108.52.160.112") enum { TREE_NODE = 0, @@ -17,8 +17,9 @@ enum { enum { TREE_CMD_IDENTIFY = 0, - TREE_CMD_STATUS, - TREE_CMD_SEARCH, - TREE_CMD_RESUME_SEARCH, - TREE_CMD_ADD + TREE_CMD_UPDATE_STATE, + TREE_CMD_CREATE_SEARCH, + TREE_CMD_GET_PAGE, + TREE_CMD_ADD, + TREE_CMD_SKIP }; diff --git a/src/tree/list.c b/src/tree/list.c index c1630b7..948d3cc 100644 --- a/src/tree/list.c +++ b/src/tree/list.c @@ -1,3 +1,5 @@ +#include <al/random.h> + #ifdef AKIYO_HAS_CURL #include "../cache/handlers/http.h" #endif @@ -14,11 +16,13 @@ void tree_list_init(struct tree_list *list, struct tree_server *tree, str *name) al_str_clone(&list->name, name); list->set = -1; list->current = 0; + list->backwards = false; al_array_init(list->entries); al_array_init(list->sinks); list->tree = tree; } +/* static void send_buffer_cmd(struct tree_list *list, struct tree_sink *sink, struct tree_list_entry *entry) { struct aki_packet *packet = aki_rpc_get_packet(&list->tree->server, CAMU_SINK_CMD_BUFFER); @@ -27,20 +31,33 @@ static void send_buffer_cmd(struct tree_list *list, struct tree_sink *sink, stru aki_packet_write_u16(packet, entry->node_id); aki_rpc_connection_command(sink->conn, packet, NULL, NULL); } +*/ static void send_set_cmd(struct tree_list *list, struct tree_sink *sink, struct tree_list_entry *entry) { struct aki_packet *packet = aki_rpc_get_packet(&list->tree->server, CAMU_SINK_CMD_SET); + aki_packet_write_str(packet, TREE_SERVER_IP); + aki_packet_write_s32(packet, TREE_STREAM_PORT); + aki_packet_write_u16(packet, entry->node_id); + aki_rpc_connection_command(sink->conn, packet, NULL, NULL); +} + +/* +static void send_queue_cmd(struct tree_list *list, struct tree_sink *sink, struct tree_list_entry *entry) +{ + struct aki_packet *packet = aki_rpc_get_packet(&list->tree->server, CAMU_SINK_CMD_QUEUE); + aki_packet_write_str(packet, TREE_SERVER_IP); + aki_packet_write_s32(packet, TREE_STREAM_PORT); aki_packet_write_u16(packet, entry->node_id); aki_rpc_connection_command(sink->conn, packet, NULL, NULL); } +*/ void tree_list_add_sink(struct tree_list *list, struct tree_sink *sink) { al_array_push(list->sinks, sink); if (list->set == list->current) { - struct tree_list_entry *entry = al_array_at(list->entries, list->current); - send_buffer_cmd(list, sink, entry); + struct tree_list_entry *entry = &al_array_at(list->entries, list->current); send_set_cmd(list, sink, entry); } } @@ -62,21 +79,16 @@ bool tree_list_add(struct tree_list *list, str *unique_id, u32 index) struct sho_post *post = sho_post_cache_get(&tree->resources.cache, unique_id); if (!post) return false; - struct tree_list_entry *entry = al_alloc_object(struct tree_list_entry); - - entry->post = post; - entry->index = index; - - entry->entry = cch_handler_http_create(&al_array_at(entry->post->media, entry->index).url); - if (!entry->entry) { - al_free(entry); - return false; - } - entry->entry->handler->maybe_spawn_worker(entry->entry->handler, 0); + struct tree_list_entry entry; + entry.index = index; + al_str_clone(&entry.unique_id, unique_id); + entry.post = post; - entry->node_id = bmu_server_create_node(&tree->streams.server, entry->entry); + entry.entry = cch_handler_http_create(&al_array_at(entry.post->media, entry.index).url); + if (!entry.entry) return false; + entry.entry->handler->maybe_spawn_worker(entry.entry->handler, 0); - entry->buffer_requested = false; + entry.node_id = bmu_server_create_node(&tree->streams.server, entry.entry); al_array_push(list->entries, entry); @@ -85,21 +97,79 @@ bool tree_list_add(struct tree_list *list, str *unique_id, u32 index) return true; } +static struct cch_entry *entry_for_external_path(struct sho_client *client, str *path) +{ + struct cch_entry *entry = NULL; +#ifdef AKIYO_HAS_CURL + bool is_search = al_str_at(path, 0) == ';'; + if (is_search || al_str_cmp(path, al_str_c("https://"), 0, 8) == 0 || + al_str_cmp(path, al_str_c("http://"), 0, 7) == 0) { + str module; + str query; + al_str_from(&module, ""); + al_str_from(&query, ""); + if (al_str_cmp(path, al_str_c("https://twitter.com"), 0, 19) == 0 || + al_str_cmp(path, al_str_c("https://x.com"), 0, 13) == 0) { + al_str_cat(&query, al_str_c("tweet:")); + al_str_cat(&query, path); + al_str_cat(&module, al_str_c("twitter")); + } else if (al_str_cmp(path, al_str_c("https://instagram.com"), 0, 21) == 0) { + s32 slash = al_str_rfind(path, '/'); + if (slash >= 0) { + al_str_cat(&query, al_str_substr(path, slash + 1, path->len)); + } + al_str_cat(&module, al_str_c("instagram")); + } else { + if (is_search) { + al_str_cat(&query, al_str_substr(path, 1, path->len)); + } else { + al_str_cat(&query, al_str_c("link:")); + al_str_cat(&query, path); + } + al_str_cat(&module, al_str_c("youtube")); + } + s32 id = sho_client_create_search(client, &module, &query); + al_str_free(&module); + al_str_free(&query); + if (id < 0) return NULL; + struct sho_search *search = sho_client_get_search(client, id); + if (!search || !sho_search_get_page(search, 0)) return NULL; + struct sho_result_page *page = &al_array_at(search->pages, 0); + struct sho_post *post; + al_array_foreach_ptr(page->posts, i, post) { + struct sho_post_media *media; + al_array_foreach_ptr(post->media, j, media) { + if (media->url.len > 0) { + entry = cch_handler_http_create(&media->url); + break; + } + } + if (entry) break; + } + sho_client_discard_search(client, id); + if (entry) entry->handler->maybe_spawn_worker(entry->handler, 0); + } else // { +#endif + entry = cch_handler_file_create(path); + // } + return entry; +} + bool tree_list_add_external(struct tree_list *list, str *path) { struct tree_server *tree = list->tree; - struct tree_list_entry *entry = al_alloc_object(struct tree_list_entry); + struct tree_list_entry entry; + entry.index = 0; + al_str_clone(&entry.unique_id, path); + entry.post = NULL; - entry->entry = cch_handler_file_create(path); - if (!entry->entry) { - al_free(entry); - return false; - } + struct tree_user *user = al_array_at(tree->users, 0); - entry->node_id = bmu_server_create_node(&tree->streams.server, entry->entry); + entry.entry = entry_for_external_path(&user->search, path); + if (!entry.entry) return false; - entry->buffer_requested = false; + entry.node_id = bmu_server_create_node(&tree->streams.server, entry.entry); al_array_push(list->entries, entry); @@ -115,25 +185,63 @@ void tree_list_skip(struct tree_list *list, s32 n) return; } list->current += n; + list->backwards = n < 0; tree_list_pump(list); } +static s32 sort_list_func(void *_a, void *_b) +{ + struct tree_list_entry *a = (struct tree_list_entry *)_a; + struct tree_list_entry *b = (struct tree_list_entry *)_b; + return al_str_cmp(&a->unique_id, &b->unique_id, 0, a->unique_id.len); +} + +void tree_list_sort(struct tree_list *list) +{ + al_array_sort(list->entries, struct tree_list_entry, sort_list_func); +} + +void tree_list_shuffle(struct tree_list *list) +{ + u32 size = list->entries.size; + if (size == 0) return; + for (u32 i = 0; i < size - 1; i++) { + u32 j = i + al_rand() / (AL_RAND_MAX / (size - i) + 1); + struct tree_list_entry tmp = al_array_at(list->entries, j); + al_array_at(list->entries, j) = al_array_at(list->entries, i); + al_array_at(list->entries, i) = tmp; + } + list->set = -1; + tree_list_pump(list); +} + +void tree_list_clear(struct tree_list *list) +{ + //list->entries.size = 0; +} + void tree_list_pump(struct tree_list *list) { s32 size = (s32)list->entries.size; if (size <= list->current) return; struct tree_sink *sink; - if (list->set != list->current) { - struct tree_list_entry *entry = al_array_at(list->entries, list->current); - if (!entry->buffer_requested) { - al_array_foreach(list->sinks, i, sink) { - send_buffer_cmd(list, sink, entry); - } - entry->buffer_requested = true; - } - al_array_foreach(list->sinks, i, sink) { - send_set_cmd(list, sink, entry); - } - list->set = list->current; + if (list->current + 1 < size) { + // This needs a massive rethinking on how to sync the lists. + // - Query sinks on server side skip request? + //struct tree_list_entry *upcoming = &al_array_at(list->entries, list->current + 1); + //al_array_foreach(list->sinks, i, sink) { + // send_queue_cmd(list, sink, upcoming); + //} } + if (list->set == list->current) return; + struct tree_list_entry *entry = &al_array_at(list->entries, list->current); + al_array_foreach(list->sinks, i, sink) { + send_set_cmd(list, sink, entry); + } + list->set = list->current; +} + +void tree_list_free(struct tree_list *list) +{ + al_str_free(&list->name); } diff --git a/src/tree/list.h b/src/tree/list.h index 3defe9f..3702983 100644 --- a/src/tree/list.h +++ b/src/tree/list.h @@ -6,11 +6,11 @@ #include "../cache/entry.h" struct tree_list_entry { - struct sho_post *post; u32 index; - u16 node_id; - bool buffer_requested; + str unique_id; + struct sho_post *post; struct cch_entry *entry; + u16 node_id; }; struct tree_sink; @@ -18,7 +18,8 @@ struct tree_list { str name; s32 set; s32 current; - array(struct tree_list_entry *) entries; + bool backwards; + array(struct tree_list_entry) entries; array(struct tree_sink *) sinks; struct tree_server *tree; }; @@ -29,4 +30,8 @@ void tree_list_remove_sink(struct tree_list *list, struct tree_sink *sink); bool tree_list_add(struct tree_list *list, str *unique_id, u32 index); bool tree_list_add_external(struct tree_list *list, str *path); void tree_list_skip(struct tree_list *list, s32 n); +void tree_list_sort(struct tree_list *list); +void tree_list_shuffle(struct tree_list *list); +void tree_list_clear(struct tree_list *list); void tree_list_pump(struct tree_list *list); +void tree_list_free(struct tree_list *list); diff --git a/src/tree/tree.c b/src/tree/tree.c index 367e0ff..8dcc501 100644 --- a/src/tree/tree.c +++ b/src/tree/tree.c @@ -68,17 +68,33 @@ static struct tree_user *get_user_by_connection(struct tree_server *tree, struct return user; } } + struct tree_list *list; + al_array_foreach(user->lists, j, list) { + struct tree_sink *sink; + al_array_foreach(list->sinks, k, sink) { + if (sink->conn == conn) { + return user; + } + } + } } + return NULL; } -static void send_status(struct tree_server *server, struct tree_user *user, struct aki_rpc_connection *conn) +static void send_current_state(struct tree_server *server, struct tree_user *user, struct aki_rpc_connection *conn) { - struct aki_packet *packet = aki_rpc_get_packet(&server->server, TREE_CMD_STATUS); + struct aki_packet *packet = aki_rpc_get_packet(&server->server, TREE_CMD_UPDATE_STATE); aki_packet_write_u32(packet, user->lists.size); struct tree_list *list; al_array_foreach(user->lists, i, list) { aki_packet_write_str(packet, &list->name); + aki_packet_write_u32(packet, list->entries.size); + struct tree_list_entry *entry; + al_array_foreach_ptr(list->entries, i, entry) { + aki_packet_write_str(packet, &entry->unique_id); + } + aki_packet_write_s32(packet, list->current); } aki_packet_write_u32(packet, user->search.searches.size); struct sho_search *search; @@ -86,7 +102,7 @@ static void send_status(struct tree_server *server, struct tree_user *user, stru aki_packet_write_s32(packet, search->id); aki_packet_write_str(packet, &search->module); aki_packet_write_str(packet, &search->query); - aki_packet_write_s32(packet, search->page); + aki_packet_write_u32(packet, search->last_page); } aki_rpc_connection_command(conn, packet, NULL, NULL); } @@ -114,7 +130,7 @@ static bool identify_command_callback(void *userdata, struct aki_rpc_connection client->user = user; al_array_push(tree->clients, client); al_array_push(user->clients, client); - send_status(tree, user, conn); + send_current_state(tree, user, conn); al_log_info("tree", "User \"%.*s\" logged in.", AL_STR_PRINTF(&user->username)); } break; @@ -134,7 +150,7 @@ static bool identify_command_callback(void *userdata, struct aki_rpc_connection return true; } -static bool search_command_callback(void *userdata, struct aki_rpc_connection *conn, +static bool create_search_command_callback(void *userdata, struct aki_rpc_connection *conn, struct aki_packet *packet, struct aki_packet *rpacket) { struct tree_server *tree = (struct tree_server *)userdata; @@ -143,17 +159,18 @@ static bool search_command_callback(void *userdata, struct aki_rpc_connection *c aki_packet_write_s32(rpacket, -1); goto out; } + str module, query; aki_packet_read_str(packet, &module); aki_packet_read_str(packet, &query); - s32 id = sho_client_create_search(&user->search, &module, &query); - aki_packet_write_s32(rpacket, id); + aki_packet_write_s32(rpacket, sho_client_create_search(&user->search, &module, &query)); + out: aki_packet_free(packet); return true; } -static bool resume_search_command_callback(void *userdata, struct aki_rpc_connection *conn, +static bool get_page_command_callback(void *userdata, struct aki_rpc_connection *conn, struct aki_packet *packet, struct aki_packet *rpacket) { struct tree_server *tree = (struct tree_server *)userdata; @@ -162,28 +179,25 @@ static bool resume_search_command_callback(void *userdata, struct aki_rpc_connec aki_packet_write_s32(rpacket, -1); goto out; } + s32 id = aki_packet_read_s32(packet); - s32 req_page = aki_packet_read_s32(packet); + u32 page_request = aki_packet_read_u32(packet); + struct sho_search *search = sho_client_get_search(&user->search, id); if (!search) { aki_packet_write_s32(rpacket, -1); goto out; } - if (req_page >= 0) { - if (!sho_search_from_page(search, req_page)) { - aki_packet_write_s32(rpacket, -1); - goto out; - } - } else { - if (!sho_search_more_results(search)) { - aki_packet_write_s32(rpacket, -1); - goto out; - } + + if (!sho_search_get_page(search, page_request)) { + aki_packet_write_s32(rpacket, -1); + goto out; } + aki_packet_write_s32(rpacket, 0); aki_packet_write_s32(rpacket, search->id); - aki_packet_write_s32(rpacket, search->page); - struct sho_result_page *page = &al_array_at(search->pages, search->page); + aki_packet_write_u32(rpacket, page_request); + struct sho_result_page *page = &al_array_at(search->pages, page_request); aki_packet_write_u32(rpacket, page->posts.size); struct sho_post *post; al_array_foreach_ptr(page->posts, i, post) { @@ -194,6 +208,7 @@ static bool resume_search_command_callback(void *userdata, struct aki_rpc_connec al_array_foreach_ptr(page->list, i, unique_id) { aki_packet_write_str(rpacket, unique_id); } + out: aki_packet_free(packet); return true; @@ -220,6 +235,24 @@ out: return false; } +static bool skip_command_callback(void *userdata, struct aki_rpc_connection *conn, + struct aki_packet *packet, struct aki_packet *rpacket) +{ + struct tree_server *tree = (struct tree_server *)userdata; + (void)rpacket; + struct tree_user *user = get_user_by_connection(tree, conn); + if (!user) goto out; + + s32 n = aki_packet_read_s32(packet); + + struct tree_list *list = al_array_at(user->lists, 0); + tree_list_skip(list, n); + +out: + aki_packet_free(packet); + return false; +} + static void connection_callback(void *userdata, struct aki_rpc_connection *conn) { (void)userdata; @@ -282,9 +315,10 @@ static void connection_closed_callback(void *userdata, struct aki_rpc_connection static struct aki_rpc_command commands[] = { { .op = TREE_CMD_IDENTIFY, .callback = identify_command_callback, .userdata = NULL }, // Client commands. - { .op = TREE_CMD_SEARCH, .callback = search_command_callback, .userdata = NULL }, - { .op = TREE_CMD_RESUME_SEARCH, .callback = resume_search_command_callback, .userdata = NULL }, - { .op = TREE_CMD_ADD, .callback = add_command_callback, .userdata = NULL } + { .op = TREE_CMD_CREATE_SEARCH, .callback = create_search_command_callback, .userdata = NULL }, + { .op = TREE_CMD_GET_PAGE, .callback = get_page_command_callback, .userdata = NULL }, + { .op = TREE_CMD_ADD, .callback = add_command_callback, .userdata = NULL }, + { .op = TREE_CMD_SKIP, .callback = skip_command_callback, .userdata = NULL } }; static bool open_user(struct tree_server *tree, struct aki_dir_entry *dir) @@ -347,6 +381,11 @@ static u8 line_callback(void *userdata, str *line) } else if (al_str_eq(line, al_str_c(";PREV"))) { tree_list_skip(list, -1); } else if (al_str_eq(line, al_str_c(";SHUFFLE"))) { + tree_list_shuffle(list); + } else if (al_str_eq(line, al_str_c(";SORT"))) { + tree_list_sort(list); + } else if (al_str_eq(line, al_str_c(";CLEAR"))) { + tree_list_clear(list); } else { tree_list_add_external(list, line); } @@ -402,12 +441,12 @@ s32 main(void) tree.socket.type = AKI_SOCKET_UNIX; aki_socket_init(&tree.socket); aki_socket_set_blocking(&tree.socket, false); - tree.pro.callback = line_callback; - tree.pro.userdata = &tree; - aki_line_processor_init(&tree.pro, al_str_c("\n")); - aki_line_processor_open_socket(&tree.pro, &tree.socket); + tree.cli.callback = line_callback; + tree.cli.userdata = &tree; + aki_line_processor_init(&tree.cli, al_str_c("\n")); + aki_line_processor_open_socket(&tree.cli, &tree.socket); if (aki_socket_listen(&tree.socket, al_str_c("/tmp/tree_sock"), 0)) { - aki_line_processor_run(&tree.pro, &tree.loop); + aki_line_processor_run(&tree.cli, &tree.loop); } #endif diff --git a/src/tree/tree.h b/src/tree/tree.h index 6339889..9f8a19a 100644 --- a/src/tree/tree.h +++ b/src/tree/tree.h @@ -53,6 +53,6 @@ struct tree_server { } streams; #if TREE_USE_SOCKET struct aki_socket socket; - struct aki_line_processor pro; + struct aki_line_processor cli; #endif }; |