summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2024-01-27 20:39:25 -0500
committerAndrew Opalach <andrew@akon.city> 2024-01-27 20:39:25 -0500
commitb100eb175e0cfa59a15c4125f26ab5474c2347ec (patch)
treef1ee4f8bed5d3490ba5d70a6fc194f3206df8df4 /src
parentfb1cbf546300d9f8efdf21c6ac66f7c2f1dfd4f3 (diff)
downloadcamu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.tar.gz
camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.tar.bz2
camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.zip
wip
- Mostly work in the client Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src')
-rw-r--r--src/bimu/vcr.c4
-rw-r--r--src/bimu/vcr.h2
-rw-r--r--src/buffer/audio.c2
-rw-r--r--src/buffer/clock.c4
-rw-r--r--src/buffer/frame_queue.h2
-rw-r--r--src/buffer/meson.build6
-rw-r--r--src/buffer/peak_buffer.c2
-rw-r--r--src/buffer/video.c3
-rw-r--r--src/cache/handlers/http.c26
-rw-r--r--src/codec/libav/decoder.c1
-rw-r--r--src/codec/libav/demuxer.c8
-rw-r--r--src/fruits/cmc/cmc.c901
-rw-r--r--src/fruits/sink/sink.c4
-rw-r--r--src/libclient/client.c97
-rw-r--r--src/libclient/client.h26
-rw-r--r--src/libclient/commands.h1
-rw-r--r--src/libclient/resource_client.c2
-rw-r--r--src/libsink/common.h3
-rw-r--r--src/libsink/sink.c247
-rw-r--r--src/libsink/sink.h2
-rw-r--r--src/screen/screen.c3
-rw-r--r--src/tree/common.h13
-rw-r--r--src/tree/list.c178
-rw-r--r--src/tree/list.h13
-rw-r--r--src/tree/tree.c97
-rw-r--r--src/tree/tree.h2
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
};