Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "ThreadCatalogModel.h"
28#include <AK/JsonArray.h>
29#include <AK/JsonObject.h>
30#include <AK/JsonValue.h>
31#include <LibCore/HttpRequest.h>
32#include <LibCore/NetworkJob.h>
33#include <LibCore/NetworkResponse.h>
34#include <stdio.h>
35
36ThreadCatalogModel::ThreadCatalogModel()
37{
38 update();
39}
40
41ThreadCatalogModel::~ThreadCatalogModel()
42{
43}
44
45void ThreadCatalogModel::set_board(const String& board)
46{
47 if (m_board == board)
48 return;
49 m_board = board;
50 update();
51}
52
53void ThreadCatalogModel::update()
54{
55 Core::HttpRequest request;
56 request.set_url(String::format("http://a.4cdn.org/%s/catalog.json", m_board.characters()));
57
58 if (m_pending_job)
59 m_pending_job->cancel();
60 m_pending_job = request.schedule();
61
62 if (on_load_started)
63 on_load_started();
64
65 m_pending_job->on_finish = [this](bool success) {
66 auto* response = m_pending_job->response();
67 dbg() << "Catalog download finished, success=" << success << ", response=" << response;
68
69 if (!success) {
70 if (on_load_finished)
71 on_load_finished(false);
72 return;
73 }
74
75 dbg() << "Catalog payload size: " << response->payload().size();
76
77 auto json = JsonValue::from_string(response->payload());
78
79 if (json.is_array()) {
80 JsonArray new_catalog;
81
82 for (auto& page : json.as_array().values()) {
83 if (!page.is_object())
84 continue;
85 auto threads_value = page.as_object().get("threads");
86 if (!threads_value.is_array())
87 continue;
88 for (auto& thread : threads_value.as_array().values()) {
89 new_catalog.append(thread);
90 }
91 }
92
93 m_catalog = move(new_catalog);
94 }
95
96 did_update();
97
98 if (on_load_finished)
99 on_load_finished(true);
100 };
101}
102
103int ThreadCatalogModel::row_count(const GUI::ModelIndex&) const
104{
105 return m_catalog.size();
106}
107
108String ThreadCatalogModel::column_name(int column) const
109{
110 switch (column) {
111 case Column::ThreadNumber:
112 return "#";
113 case Column::Subject:
114 return "Subject";
115 case Column::Text:
116 return "Text";
117 case Column::ReplyCount:
118 return "Replies";
119 case Column::ImageCount:
120 return "Images";
121 case Column::PostTime:
122 return "Time";
123 default:
124 ASSERT_NOT_REACHED();
125 }
126}
127
128GUI::Model::ColumnMetadata ThreadCatalogModel::column_metadata(int column) const
129{
130 switch (column) {
131 case Column::ThreadNumber:
132 return { 70, Gfx::TextAlignment::CenterRight };
133 case Column::Subject:
134 return { 170, Gfx::TextAlignment::CenterLeft };
135 case Column::Text:
136 return { 270, Gfx::TextAlignment::CenterLeft };
137 case Column::ReplyCount:
138 return { 45, Gfx::TextAlignment::CenterRight };
139 case Column::ImageCount:
140 return { 40, Gfx::TextAlignment::CenterRight };
141 case Column::PostTime:
142 return { 120, Gfx::TextAlignment::CenterLeft };
143 default:
144 ASSERT_NOT_REACHED();
145 }
146}
147
148GUI::Variant ThreadCatalogModel::data(const GUI::ModelIndex& index, Role role) const
149{
150 auto& thread = m_catalog.at(index.row()).as_object();
151 if (role == Role::Display) {
152 switch (index.column()) {
153 case Column::ThreadNumber:
154 return thread.get("no").to_u32();
155 case Column::Subject:
156 return thread.get("sub").as_string_or({});
157 case Column::Text:
158 return thread.get("com").as_string_or({});
159 case Column::ReplyCount:
160 return thread.get("replies").to_u32();
161 case Column::ImageCount:
162 return thread.get("images").to_u32();
163 case Column::PostTime:
164 return thread.get("now").to_string();
165 default:
166 ASSERT_NOT_REACHED();
167 }
168 }
169 return {};
170}