mirror of https://github.com/kurisufriend/Clover
A lot of random stuff, but mostly support for having Site and Board /objects/ on the database models. Start of interface for a wide range of sites.multisite
parent
a613a0dec4
commit
3a652cf51e
@ -0,0 +1,83 @@ |
|||||||
|
package org.floens.chan.core.database; |
||||||
|
|
||||||
|
import org.floens.chan.core.model.Board; |
||||||
|
import org.floens.chan.core.site.Site; |
||||||
|
import org.floens.chan.core.site.Sites; |
||||||
|
import org.floens.chan.utils.Logger; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
public class DatabaseBoardManager { |
||||||
|
private static final String TAG = "DatabaseBoardManager"; |
||||||
|
|
||||||
|
private DatabaseManager databaseManager; |
||||||
|
private DatabaseHelper helper; |
||||||
|
|
||||||
|
public DatabaseBoardManager(DatabaseManager databaseManager, DatabaseHelper helper) { |
||||||
|
this.databaseManager = databaseManager; |
||||||
|
this.helper = helper; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Save the boards listed in the database.<br> |
||||||
|
* The boards need to either come from a {@link Site} or from {@link #getBoards(Site)}. |
||||||
|
* |
||||||
|
* @param boards boards to set or update |
||||||
|
* @return void |
||||||
|
*/ |
||||||
|
public Callable<Void> setBoards(final List<Board> boards) { |
||||||
|
return new Callable<Void>() { |
||||||
|
@Override |
||||||
|
public Void call() throws Exception { |
||||||
|
for (Board b : boards) { |
||||||
|
helper.boardsDao.createOrUpdate(b); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public Callable<List<Board>> getBoards(final Site site) { |
||||||
|
return new Callable<List<Board>>() { |
||||||
|
@Override |
||||||
|
public List<Board> call() throws Exception { |
||||||
|
List<Board> boards = null; |
||||||
|
try { |
||||||
|
boards = helper.boardsDao.queryBuilder() |
||||||
|
.where().eq("site", site.id()) |
||||||
|
.query(); |
||||||
|
for (int i = 0; i < boards.size(); i++) { |
||||||
|
Board board = boards.get(i); |
||||||
|
board.site = site; |
||||||
|
} |
||||||
|
} catch (SQLException e) { |
||||||
|
Logger.e(TAG, "Error getting boards from db", e); |
||||||
|
} |
||||||
|
|
||||||
|
return boards; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public Callable<List<Board>> getAllBoards() { |
||||||
|
return new Callable<List<Board>>() { |
||||||
|
@Override |
||||||
|
public List<Board> call() throws Exception { |
||||||
|
List<Board> boards = null; |
||||||
|
try { |
||||||
|
boards = helper.boardsDao.queryForAll(); |
||||||
|
for (int i = 0; i < boards.size(); i++) { |
||||||
|
Board board = boards.get(i); |
||||||
|
board.site = Sites.forId(board.siteId); |
||||||
|
} |
||||||
|
} catch (SQLException e) { |
||||||
|
Logger.e(TAG, "Error getting boards from db", e); |
||||||
|
} |
||||||
|
return boards; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package org.floens.chan.core.site; |
||||||
|
|
||||||
|
import org.floens.chan.core.model.Board; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class Boards { |
||||||
|
public final List<Board> boards; |
||||||
|
|
||||||
|
public Boards(List<Board> boards) { |
||||||
|
this.boards = boards; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
package org.floens.chan.core.site; |
||||||
|
|
||||||
|
import org.floens.chan.core.model.Board; |
||||||
|
|
||||||
|
public interface Site { |
||||||
|
enum Feature { |
||||||
|
/** |
||||||
|
* This site supports posting. (Or rather, we've implemented support for it.) |
||||||
|
*/ |
||||||
|
POSTING, |
||||||
|
|
||||||
|
/** |
||||||
|
* This site supports a 4chan like boards.json endpoint. |
||||||
|
*/ |
||||||
|
DYNAMIC_BOARDS, |
||||||
|
|
||||||
|
/** |
||||||
|
* This site supports deleting posts. |
||||||
|
*/ |
||||||
|
POST_DELETE, |
||||||
|
|
||||||
|
/** |
||||||
|
* This site supports some sort of login (like 4pass). |
||||||
|
*/ |
||||||
|
LOGIN |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Features available to check when {@link Feature#POSTING} is {@code true}. |
||||||
|
*/ |
||||||
|
enum BoardFeature { |
||||||
|
/** |
||||||
|
* This board supports posting with images. |
||||||
|
*/ |
||||||
|
POSTING_IMAGE, |
||||||
|
|
||||||
|
/** |
||||||
|
* This board supports posting with a checkbox to mark the posted image as a spoiler. |
||||||
|
*/ |
||||||
|
POSTING_SPOILER, |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* How the boards are organized for this size. |
||||||
|
*/ |
||||||
|
enum BoardsType { |
||||||
|
/** |
||||||
|
* The site's boards are static, hard-coded in the site. |
||||||
|
*/ |
||||||
|
STATIC, |
||||||
|
|
||||||
|
/** |
||||||
|
* The site's boards are dynamic, a boards.json like endpoint is available to get the available boards. |
||||||
|
*/ |
||||||
|
DYNAMIC, |
||||||
|
|
||||||
|
/** |
||||||
|
* The site's boards are dynamic and infinite, existence of boards should be checked per board. |
||||||
|
*/ |
||||||
|
INFINITE |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Global positive (>0) integer that uniquely identifies this site.<br> |
||||||
|
* This id will be persisted in the database. |
||||||
|
* |
||||||
|
* @return a positive (>0) integer that uniquely identifies this site. |
||||||
|
*/ |
||||||
|
int id(); |
||||||
|
|
||||||
|
boolean feature(Feature feature); |
||||||
|
|
||||||
|
boolean boardFeature(BoardFeature boardFeature, Board board); |
||||||
|
|
||||||
|
SiteEndpoints endpoints(); |
||||||
|
|
||||||
|
BoardsType boardsType(); |
||||||
|
|
||||||
|
void boards(BoardsListener boardsListener); |
||||||
|
|
||||||
|
Board board(String name); |
||||||
|
|
||||||
|
interface BoardsListener { |
||||||
|
void onBoardsReceived(Boards boards); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
package org.floens.chan.core.site; |
||||||
|
|
||||||
|
import org.floens.chan.core.model.Board; |
||||||
|
import org.floens.chan.core.model.Loadable; |
||||||
|
import org.floens.chan.core.model.Post; |
||||||
|
|
||||||
|
/** |
||||||
|
* Endpoints for {@link Site}. |
||||||
|
*/ |
||||||
|
public interface SiteEndpoints { |
||||||
|
String catalog(Board board); |
||||||
|
|
||||||
|
String thread(Board board, Loadable loadable); |
||||||
|
|
||||||
|
String imageUrl(Post post); |
||||||
|
|
||||||
|
String thumbnailUrl(Post post); |
||||||
|
|
||||||
|
String flag(Post post); |
||||||
|
|
||||||
|
String boards(); |
||||||
|
|
||||||
|
String reply(Board board, Loadable thread); |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
package org.floens.chan.core.site; |
||||||
|
|
||||||
|
import org.floens.chan.core.site.sites.chan4.Chan4; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class Sites { |
||||||
|
public static final Chan4 CHAN4 = new Chan4(); |
||||||
|
|
||||||
|
public static final List<? extends Site> ALL_SITES = Arrays.asList( |
||||||
|
CHAN4 |
||||||
|
); |
||||||
|
|
||||||
|
private static final Site[] BY_ID; |
||||||
|
|
||||||
|
static { |
||||||
|
int highestId = 0; |
||||||
|
for (Site site : ALL_SITES) { |
||||||
|
if (site.id() > highestId) { |
||||||
|
highestId = site.id(); |
||||||
|
} |
||||||
|
} |
||||||
|
BY_ID = new Site[highestId + 1]; |
||||||
|
for (Site site : ALL_SITES) { |
||||||
|
BY_ID[site.id()] = site; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static Site forId(int id) { |
||||||
|
return BY_ID[id]; |
||||||
|
} |
||||||
|
|
||||||
|
public static Site defaultSite() { |
||||||
|
return CHAN4; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,165 @@ |
|||||||
|
package org.floens.chan.core.site.sites.chan4; |
||||||
|
|
||||||
|
import com.android.volley.Response; |
||||||
|
import com.android.volley.VolleyError; |
||||||
|
|
||||||
|
import org.floens.chan.Chan; |
||||||
|
import org.floens.chan.core.model.Board; |
||||||
|
import org.floens.chan.core.model.Loadable; |
||||||
|
import org.floens.chan.core.model.Post; |
||||||
|
import org.floens.chan.core.site.Boards; |
||||||
|
import org.floens.chan.core.site.Site; |
||||||
|
import org.floens.chan.core.site.SiteEndpoints; |
||||||
|
import org.floens.chan.utils.Logger; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Locale; |
||||||
|
import java.util.Random; |
||||||
|
|
||||||
|
public class Chan4 implements Site { |
||||||
|
private static final String TAG = "Chan4"; |
||||||
|
|
||||||
|
private static final Random random = new Random(); |
||||||
|
|
||||||
|
private final SiteEndpoints endpoints = new SiteEndpoints() { |
||||||
|
@Override |
||||||
|
public String catalog(Board board) { |
||||||
|
return "https://a.4cdn.org/" + board.code + "/catalog.json"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String thread(Board board, Loadable loadable) { |
||||||
|
return "https://a.4cdn.org/" + board.code + "/thread/" + loadable.no + ".json"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String imageUrl(Post post) { |
||||||
|
return "https://i.4cdn.org/" + post.boardId + "/" + Long.toString(post.tim) + "." + post.ext; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String thumbnailUrl(Post post) { |
||||||
|
if (post.spoiler) { |
||||||
|
if (post.board.customSpoilers >= 0) { |
||||||
|
int i = random.nextInt(post.board.customSpoilers) + 1; |
||||||
|
return "https://s.4cdn.org/image/spoiler-" + post.board.code + i + ".png"; |
||||||
|
} else { |
||||||
|
return "https://s.4cdn.org/image/spoiler.png"; |
||||||
|
} |
||||||
|
} else { |
||||||
|
return "https://t.4cdn.org/" + post.board.code + "/" + post.tim + "s.jpg"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String flag(Post post) { |
||||||
|
return "https://s.4cdn.org/image/country/" + post.country.toLowerCase(Locale.ENGLISH) + ".gif"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String boards() { |
||||||
|
return "https://a.4cdn.org/boards.json"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String reply(Board board, Loadable thread) { |
||||||
|
return "https://sys.4chan.org/" + board.code + "/post"; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
public Chan4() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* <b>Note: very special case, only this site may have 0 as the return value.<br> |
||||||
|
* This is for backwards compatibility when we didn't support multi-site yet.</b> |
||||||
|
* |
||||||
|
* @return {@inheritDoc} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public int id() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean feature(Feature feature) { |
||||||
|
switch (feature) { |
||||||
|
case POSTING: |
||||||
|
// yes, we support posting.
|
||||||
|
return true; |
||||||
|
case DYNAMIC_BOARDS: |
||||||
|
// yes, boards.json
|
||||||
|
return true; |
||||||
|
case LOGIN: |
||||||
|
// 4chan pass.
|
||||||
|
return true; |
||||||
|
case POST_DELETE: |
||||||
|
// yes, with the password saved when posting.
|
||||||
|
return true; |
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoardsType boardsType() { |
||||||
|
return BoardsType.DYNAMIC; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean boardFeature(BoardFeature boardFeature, Board board) { |
||||||
|
switch (boardFeature) { |
||||||
|
case POSTING_IMAGE: |
||||||
|
// yes, we support image posting.
|
||||||
|
return true; |
||||||
|
case POSTING_SPOILER: |
||||||
|
// depends if the board supports it.
|
||||||
|
return board.spoilers; |
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Board board(String name) { |
||||||
|
List<Board> allBoards = Chan.getBoardManager().getAllBoards(); |
||||||
|
for (Board board : allBoards) { |
||||||
|
if (board.code.equals(name)) { |
||||||
|
return board; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SiteEndpoints endpoints() { |
||||||
|
return endpoints; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void boards(final BoardsListener listener) { |
||||||
|
Chan.getVolleyRequestQueue().add(new Chan4BoardsRequest(this, new Response.Listener<List<Board>>() { |
||||||
|
@Override |
||||||
|
public void onResponse(List<Board> response) { |
||||||
|
listener.onBoardsReceived(new Boards(response)); |
||||||
|
} |
||||||
|
}, new Response.ErrorListener() { |
||||||
|
@Override |
||||||
|
public void onErrorResponse(VolleyError error) { |
||||||
|
Logger.e(TAG, "Failed to get boards from server", error); |
||||||
|
|
||||||
|
// API fail, provide some default boards
|
||||||
|
List<Board> list = new ArrayList<>(); |
||||||
|
list.add(new Board(Chan4.this, "Technology", "g", true, true)); |
||||||
|
list.add(new Board(Chan4.this, "Food & Cooking", "ck", true, true)); |
||||||
|
list.add(new Board(Chan4.this, "Do It Yourself", "diy", true, true)); |
||||||
|
list.add(new Board(Chan4.this, "Animals & Nature", "an", true, true)); |
||||||
|
Collections.shuffle(list); |
||||||
|
listener.onBoardsReceived(new Boards(list)); |
||||||
|
} |
||||||
|
})); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- |
||||||
|
Clover - 4chan browser https://github.com/Floens/Clover/ |
||||||
|
Copyright (C) 2014 Floens |
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
--> |
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="48dp" |
||||||
|
android:orientation="horizontal"> |
||||||
|
|
||||||
|
<EditText |
||||||
|
android:id="@+id/input" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:ellipsize="end" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:inputType="text" |
||||||
|
android:maxLines="1" |
||||||
|
android:paddingLeft="16dp" |
||||||
|
android:paddingRight="16dp" |
||||||
|
android:textColor="?text_color_primary" |
||||||
|
android:textSize="14sp" /> |
||||||
|
|
||||||
|
</LinearLayout> |
Loading…
Reference in new issue