#include #include #include #include "json.h" #include "TvShows.h" #include "CurlResult.h" #include "Colors.h" #include "EpisodeList.h" #include "EpisodePage.h" #include "utils.h" void TvShows_init(struct TvShows* this) { this->lists = List_create(); this->selectedList = NULL; this->scrollY = 0; Search_init(&this->search); int x, y; getmaxyx(stdscr, y,x); this->window = newwin(y-PLAYER_H, x, 0, 0); scrollok(this->window, true); } void TvShows_destroyMembers(struct TvShows* this) { Search_destroyMembers(&this->search); List_destroyWithContent(this->lists, TvShowList_destroy); } DEFAULT_CREATE_DESTROY(TvShows) bool TvShows_handleInput(struct TvShows* this, int c) { if ((this != *this->activePage && c != KEY_RESIZE)) { return false; } if (Search_handleInput(&this->search, c)) { Search_draw(&this->search, this->window); if (this->search.status == searchBrowse) { TvShows_searchForward(this); } return true; } if (c == 'n' || c == 'j' || c == KEY_DOWN) { if (this->search.status == searchBrowse) { TvShows_searchForward(this); } else { TvShows_selectDelta(this, +1); } } else if (c == 'p' || c == 'k' || c == KEY_UP) { if (this->search.status == searchBrowse) { TvShows_searchBackward(this); } else { TvShows_selectDelta(this, -1); } } else if (c == PAGE_UP) { TvShows_selectDelta(this, -10); } else if (c == PAGE_DOWN) { TvShows_selectDelta(this, +10); } else if (c == 'f' || c == 'l' || c == KEY_RIGHT) { struct TvShowLi* tvShow = TvShows_selectedTvShow(this); if (tvShow) { *this->activePage = this->episodePage; safe_free(this->episodePage->name); this->episodePage->name = tvShow->name ? copy_string(tvShow->name, strlen(tvShow->name)) : NULL; EpisodePage_fetch(this->episodePage, tvShow->name); EpisodePage_activate(this->episodePage); } } else if (c == KEY_HOME || c == '<') { TvShows_selectFirstOrLast(this, true); } else if (c == KEY_END || c == '>') { TvShows_selectFirstOrLast(this, false); } else if (c == '/') { if (searchRead != this->search.status) { Search_clear(&this->search); } this->search.status = searchRead; } else if (c == 'g') { this->search.status = searchInactive; Search_clear(&this->search); } else if (c == '\n' || c == 'a') { TvShows_playSelected(this); } else if (c == 'u') { TvShows_fetch(this, this->baseUrl); } else if (c == KEY_RESIZE) { int screenX, screenY; getmaxyx(stdscr, screenY, screenX); wresize(this->window, screenY-PLAYER_H, screenX); wclear(this->window); TvShows_printAll(this, NULL); return false; } else { return false; } return true; } void TvShows_playSelected(struct TvShows* this) { struct TvShowLi* tvShow = TvShows_selectedTvShow(this); if (!tvShow) { return; } const char* name = tvShow->name; struct EpisodeList* episodeList = EpisodeList_fetch(this->baseUrl, name); EpisodeList_play(episodeList, this->baseUrl, EpisodeList_contstructPlaylist(episodeList)); EpisodeList_destroy(episodeList); } struct TvShowLi* TvShows_selectedTvShow(struct TvShows* this) { if (!this->selectedList) return NULL; struct TvShowList* list = this->selectedList->data; if (!list->selectedLi) return NULL; return list->selectedLi->data; } void TvShows_searchInDirection(struct TvShows* this, bool direction) { if (!this->search.queryUtf8) { return; } ListNode* listingNode = this->selectedList ? this->selectedList : (direction ? this->lists->first : this->lists->last); while (listingNode) { struct TvShowList* listing = listingNode->data; ListNode* liNode = direction ? (listing->selectedLi ? listing->selectedLi->next : listing->lis->first) : (listing->selectedLi ? listing->selectedLi->previous : listing->lis->last); while (liNode) { struct TvShowLi* li = liNode->data; const char* match = strstr(li->name, this->search.queryUtf8); if (match) { if (this->selectedList) { struct TvShowList* listing = this->selectedList->data; listing->selectedLi = NULL; } this->selectedList = listingNode; listing->selectedLi = liNode; TvShows_selectDelta(this, 0); return; } liNode = direction ? liNode->next : liNode->previous; } listingNode = direction ? listingNode->next : listingNode->previous; } } void TvShows_searchForward(struct TvShows* this) { TvShows_searchInDirection(this, true); } void TvShows_searchBackward(struct TvShows* this) { TvShows_searchInDirection(this, false); } void TvShows_selectFirstOrLast(struct TvShows* this, bool direction) { this->selectedList = direction ? this->lists->first : this->lists->last; if (this->selectedList) { struct TvShowList* list = this->selectedList->data; list->selectedLi = direction ? list->lis->first : list->lis->last; } TvShows_printAll(this, NULL); if (direction) { TvShows_selectDelta(this, 1); TvShows_selectDelta(this, -1); } else { TvShows_selectDelta(this, -1); TvShows_selectDelta(this, 1); } } void TvShows_selectDelta(struct TvShows* this, int delta) { if (!this->selectedList) { this->selectedList = delta >= 0 ? this->lists->first : this->lists->last; if (!this->selectedList) { return; } } struct TvShowList* list = this->selectedList->data; delta = TvShowList_selectDelta(list, this->window, delta); while (delta != 0 && this->selectedList) { if (delta > 0) { struct TvShowList* listing = this->selectedList->data; listing->selectedLi = NULL; this->selectedList = this->selectedList->next; } else if (delta < 0) { struct TvShowList* listing = this->selectedList->data; listing->selectedLi = NULL; this->selectedList = this->selectedList->previous; } if (this->selectedList) { list = this->selectedList->data; delta = TvShowList_selectDelta(list, this->window, delta); } } bool didDraw = false; if (this->selectedList) { struct TvShowList* list = this->selectedList->data; if (list->selectedLi) { struct TvShowLi* li = list->selectedLi->data; int screenY = getmaxy(stdscr); if (li->y > 1) { int offset = (screenY-PLAYER_H)/2; int newScroll = li->y - offset; newScroll = newScroll > 0 ? newScroll : 0; int diff = newScroll - this->scrollY; /* wscrl(this->window, diff); */ wclear(this->window); if (diff != 0) { int linesToUpdate[4] = { this->scrollY, this->scrollY + (screenY-1), li->y, -1}; this->scrollY = newScroll; TvShows_printAll(this, NULL); didDraw = true; } } } } if (!didDraw) { TvShows_printAll(this, NULL); } } int TvShows_printAll(struct TvShows* this, int* linesToUpdate) { if (this != *this->activePage) { return -1; } int y = 0; for (ListNode* it = this->lists->first; it; it = it->next) { struct TvShowList* data = it->data; if (data) { bool noMoreLinesToDraw = (NULL != linesToUpdate && -1 == *linesToUpdate); WINDOW* drawWindow = noMoreLinesToDraw ? NULL : this->window; struct DrawPair info = { .y = y > 0 ? (y+1) : y, /* space between lists */ .linesToUpdate = linesToUpdate }; struct DrawPair result = TvShowList_draw(data, drawWindow, this->scrollY, info); y = result.y; linesToUpdate = result.linesToUpdate; } } if (this->window) { wrefresh(this->window); } return y; } void TvShows_fetch(struct TvShows* this, const char* baseUrl) { CURL* handle = curl_easy_init(); struct CurlResult userdata; CurlResult_init(&userdata); char url[256]; sprintf(url, "%s/%s", baseUrl, "api/library/filter/lists"); curl_easy_setopt(handle, CURLOPT_URL, url); curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15); curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(handle, CURLOPT_WRITEDATA, &userdata); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, CurlResult_write); curl_easy_perform(handle); json_value* json = CurlResult_parse(&userdata); TvShows_restore(this, json); TvShows_printAll(this, NULL); wrefresh(this->window); this->baseUrl = baseUrl; json_value_free(json); CurlResult_destroyMembers(&userdata); curl_easy_cleanup(handle); } void TvShows_restore(struct TvShows* this, json_value* json) { if (!json || json->type != json_object) { printw("failed to parse json: %p\n", json); refresh(); return; } int length = json->u.object.length; for (int i=0; i < length; ++i) { char* itemName = json->u.object.values[i].name; if (0 == strcmp(itemName, "lists")) { json_value* lists = json->u.object.values[i].value; if (lists->type != json_object) { continue; } int listsLength = lists->u.object.length; for (int j=0; j < listsLength; ++j) { char* name = lists->u.object.values[j].name; json_value* list = lists->u.object.values[j].value; TvShows_restoreList(this, name, list); } } } } void TvShows_restoreList(struct TvShows* this, const char* name, json_value* json) { if (json->type != json_array) { return; } struct TvShowList* list = TvShowList_create(); bool hasLis = false; int length = json->u.array.length; for (int i=0; i < length; ++i) { json_value* value = json->u.array.values[i]; struct TvShowLi* li = TvShowLi_restore(value); if (li) { hasLis = true; List_pushBack(list->lis, li); } } if (hasLis) { list->name = copy_string(name, strlen(name)); List_qSort(list->lis, TvShowLi_compare); List_pushBack(this->lists, list); } else { TvShowList_destroy(list); } } struct TvShowLi* TvShowLi_restore(json_value* json) { if (json->type != json_object) { return NULL; } struct TvShowLi* li = malloc(sizeof(struct TvShowLi)); int showLength = json->u.object.length; for (int i=0; i < showLength; ++i) { char* key = json->u.object.values[i].name; json_value* value = json->u.object.values[i].value; if (0 == strcmp(key, "name") && value->type == json_string) { li->name = copy_string(value->u.string.ptr, value->u.string.length); } else if (0 == strcmp(key, "downloadedEpisodes") && value->type == json_integer) { li->downloadedEpisodes = value->u.integer; } else if (0 == strcmp(key, "totalEpisodes") && value->type == json_integer) { li->totalEpisodes = value->u.integer; } else if (0 == strcmp(key, "watchedEpisodes") && value->type == json_integer) { li->watchedEpisodes = value->u.integer; } } return li; } void TvShowList_init(struct TvShowList* this) { this->name = NULL; this->lis = List_create(); this->selectedLi = NULL; } void TvShowList_destroyMembers(struct TvShowList* this) { safe_free(this->name); List_destroyWithContent(this->lis, TvShowLi_destroy); } DEFAULT_CREATE_DESTROY(TvShowList) bool shouldDrawLine(struct DrawPair info, int y) { return (NULL == info.linesToUpdate || *info.linesToUpdate == y); } struct DrawPair TvShowList_draw(struct TvShowList* this, WINDOW* window, int scrollY, struct DrawPair info) { int y = info.y; int hfy = y-scrollY; /* header-final-y */ int screenY = getmaxy(stdscr); if (window && hfy > -1 && hfy < (screenY) && shouldDrawLine(info, y)) { wmove(window, hfy, 0); wattron(window, A_UNDERLINE); wprintw(window, "list %s", this->name); /*ウニコード モタファクぁ*/ wattroff(window, A_UNDERLINE); if (info.linesToUpdate) { ++info.linesToUpdate; } } ++y; for (ListNode* it = this->lis->first; it; it = it->next) { struct TvShowLi* data = it->data; if (data) { int fy = y - (scrollY); /* final-y */ if (window && y > 0 && fy < (screenY) && fy >= 0 && shouldDrawLine(info, y)) { wmove(window, fy, 0); wprintw(window, "%d", y); /* wmove(window, fy > 0 ? fy : 1, 3); */ /* waddstr(window, data->name); */ bool selected = this->selectedLi && this->selectedLi->data == data; TvShowLi_draw(data, window, selected, fy); if (info.linesToUpdate) { ++info.linesToUpdate; } } data->y = y; ++y; } } struct DrawPair result = { .y = y, .linesToUpdate = info.linesToUpdate }; return result; } int TvShowList_selectDelta(struct TvShowList* this, WINDOW* window, int delta) { /* ListNode* previous = this->selectedLi; */ if (!this->selectedLi) { if (delta > 0) { this->selectedLi = this->lis->first; if (this->selectedLi) { --delta; } } else if (delta < 0) { this->selectedLi = this->lis->last; if (this->selectedLi) { ++delta; } } } while (delta > 0 && this->selectedLi) { this->selectedLi = this->selectedLi->next; if (this->selectedLi) { --delta; } } while (delta < 0 && this->selectedLi) { this->selectedLi = this->selectedLi->previous; if (this->selectedLi) { ++delta; } } return delta; } void TvShowLi_init(struct TvShowLi* this) { this->name = NULL; this->watchedEpisodes = 0; this->downloadedEpisodes = 0; this->totalEpisodes = 0; this->y = 0; } void TvShowLi_destroyMembers(struct TvShowLi* this) { safe_free(this->name); } DEFAULT_CREATE_DESTROY(TvShowLi) void TvShowLi_draw(struct TvShowLi* li, WINDOW* window, bool selected, int y) { wmove(window, y, 3); if (selected) { Colors_wset(window, Colors_Selected); } int x = getmaxx(window); int clearChars = x - safe_strlen(li->name) - 20; /* wscrl(window, -y); */ waddstr(window, li->name); for (int i=0; i < clearChars; ++i) { waddch(window, ' '); } wprintw(window, " | %d/%d/%d", li->watchedEpisodes, li->downloadedEpisodes, li->totalEpisodes); if (selected) { Colors_wset(window, Colors_Default); } wmove(window, y, 0); wrefresh(window); } int TvShowLi_compare(void* av, void* bv) { struct TvShowLi* a = av; struct TvShowLi* b = bv; if (!a->name && !b->name) { return 0; } if (!a->name) { return -1; } if (!b->name) { return 1; } int result = strcmp(a->name, b->name); return result < 0 ? -1 : result > 0 ? 1 : 0; }