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