mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 06:24:50 +01:00
Added reactive updating to TimelineWindow and NotificationsWindow
Thread unsafe, will probably explode eventually
This commit is contained in:
parent
db304ad2df
commit
4ed5b3536c
@ -32,8 +32,8 @@ JKomasto {
|
|||||||
private NotificationsWindow
|
private NotificationsWindow
|
||||||
notificationsWindow;
|
notificationsWindow;
|
||||||
|
|
||||||
private TimelineWindowUpdater
|
private WindowUpdater
|
||||||
timelineWindowUpdater;
|
windowUpdater;
|
||||||
|
|
||||||
private MastodonApi
|
private MastodonApi
|
||||||
api;
|
api;
|
||||||
@ -46,22 +46,14 @@ JKomasto {
|
|||||||
public void
|
public void
|
||||||
finishedLogin()
|
finishedLogin()
|
||||||
{
|
{
|
||||||
autoViewWindow.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
|
||||||
timelineWindow.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
timelineWindow.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
|
|
||||||
notificationsWindow.showLatestPage();
|
|
||||||
timelineWindow.showLatestPage();
|
timelineWindow.showLatestPage();
|
||||||
timelineWindow.setLocationByPlatform(true);
|
notificationsWindow.showLatestPage();
|
||||||
timelineWindow.setVisible(true);
|
timelineWindow.setVisible(true);
|
||||||
|
loginWindow.dispose();
|
||||||
|
|
||||||
autoViewWindow.setTitle("Auto view - JKomasto");
|
|
||||||
//autoViewWindow.setVisible(true);
|
|
||||||
|
|
||||||
loginWindow.dispose();
|
|
||||||
autoViewWindow.setCursor(null);
|
|
||||||
timelineWindow.setCursor(null);
|
timelineWindow.setCursor(null);
|
||||||
|
|
||||||
timelineWindowUpdater.addWindow(timelineWindow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PostWindow
|
public PostWindow
|
||||||
@ -76,10 +68,16 @@ JKomasto {
|
|||||||
public NotificationsWindow
|
public NotificationsWindow
|
||||||
getNotificationsWindow() { return notificationsWindow; }
|
getNotificationsWindow() { return notificationsWindow; }
|
||||||
|
|
||||||
|
public WindowUpdater
|
||||||
|
getWindowUpdater() { return windowUpdater; }
|
||||||
|
|
||||||
// ---%-@-%---
|
// ---%-@-%---
|
||||||
|
|
||||||
public static void
|
public static void
|
||||||
main(String... args) { new JKomasto(); }
|
main(String... args)
|
||||||
|
{
|
||||||
|
new JKomasto().loginWindow.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
// ---%-@-%---
|
// ---%-@-%---
|
||||||
|
|
||||||
@ -87,6 +85,7 @@ JKomasto {
|
|||||||
JKomasto()
|
JKomasto()
|
||||||
{
|
{
|
||||||
api = new MastodonApi();
|
api = new MastodonApi();
|
||||||
|
windowUpdater = new WindowUpdater(this);
|
||||||
|
|
||||||
timelineWindow = new TimelineWindow(this);
|
timelineWindow = new TimelineWindow(this);
|
||||||
composeWindow = new ComposeWindow(this);
|
composeWindow = new ComposeWindow(this);
|
||||||
@ -95,15 +94,16 @@ JKomasto {
|
|||||||
mediaWindow = new ImageWindow();
|
mediaWindow = new ImageWindow();
|
||||||
notificationsWindow = new NotificationsWindow(this);
|
notificationsWindow = new NotificationsWindow(this);
|
||||||
|
|
||||||
|
autoViewWindow.setTitle("Auto view - JKomasto");
|
||||||
|
|
||||||
composeWindow.dispose();
|
composeWindow.dispose();
|
||||||
autoViewWindow.dispose();
|
autoViewWindow.dispose();
|
||||||
timelineWindow.dispose();
|
timelineWindow.dispose();
|
||||||
mediaWindow.dispose();
|
mediaWindow.dispose();
|
||||||
notificationsWindow.dispose();
|
notificationsWindow.dispose();
|
||||||
loginWindow.setLocationByPlatform(true);
|
|
||||||
loginWindow.setVisible(true);
|
|
||||||
|
|
||||||
timelineWindowUpdater = new TimelineWindowUpdater(this);
|
timelineWindow.setLocationByPlatform(true);
|
||||||
|
loginWindow.setLocationByPlatform(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import java.io.FileWriter;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.nio.channels.ClosedByInterruptException;
|
||||||
|
|
||||||
class
|
class
|
||||||
MastodonApi {
|
MastodonApi {
|
||||||
@ -440,6 +441,7 @@ MastodonApi {
|
|||||||
public void
|
public void
|
||||||
monitorTimeline(
|
monitorTimeline(
|
||||||
TimelineType type, ServerSideEventsListener handler)
|
TimelineType type, ServerSideEventsListener handler)
|
||||||
|
throws InterruptedException
|
||||||
{
|
{
|
||||||
String token = accessToken.get("access_token").value;
|
String token = accessToken.get("access_token").value;
|
||||||
|
|
||||||
@ -474,11 +476,16 @@ MastodonApi {
|
|||||||
|
|
||||||
input = new InputStreamReader(conn.getInputStream());
|
input = new InputStreamReader(conn.getInputStream());
|
||||||
BufferedReader br = new BufferedReader(input);
|
BufferedReader br = new BufferedReader(input);
|
||||||
while (true) {
|
while (true)
|
||||||
|
{
|
||||||
String line = br.readLine();
|
String line = br.readLine();
|
||||||
if (line != null) handler.lineReceived(line);
|
if (line != null) handler.lineReceived(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (ClosedByInterruptException eIt)
|
||||||
|
{
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,9 @@ NotificationsWindow extends JFrame {
|
|||||||
private NotificationsComponent
|
private NotificationsComponent
|
||||||
display;
|
display;
|
||||||
|
|
||||||
|
private boolean
|
||||||
|
showingLatest;
|
||||||
|
|
||||||
// - -%- -
|
// - -%- -
|
||||||
|
|
||||||
private static int
|
private static int
|
||||||
@ -48,7 +51,7 @@ NotificationsWindow extends JFrame {
|
|||||||
// ---%-@-%---
|
// ---%-@-%---
|
||||||
|
|
||||||
public void
|
public void
|
||||||
displayEntity(Tree<String> entity)
|
readEntity(Tree<String> entity)
|
||||||
{
|
{
|
||||||
notifications = new ArrayList<>();
|
notifications = new ArrayList<>();
|
||||||
for (Tree<String> t: entity)
|
for (Tree<String> t: entity)
|
||||||
@ -97,20 +100,45 @@ NotificationsWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
refresh()
|
||||||
|
{
|
||||||
|
String firstId = null;
|
||||||
|
if (!showingLatest)
|
||||||
|
{
|
||||||
|
assert !notifications.isEmpty();
|
||||||
|
firstId = notifications.get(0).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fetchPage(firstId, null))
|
||||||
|
{
|
||||||
|
if (notifications.size() < ROW_COUNT) showLatestPage();
|
||||||
|
display.showNotifications(notifications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
showLatestPage()
|
showLatestPage()
|
||||||
{
|
{
|
||||||
fetchPage(null, null);
|
if (fetchPage(null, null))
|
||||||
display.showNotifications(notifications);
|
{
|
||||||
|
display.showNotifications(notifications);
|
||||||
|
showingLatest = true;
|
||||||
|
primaire.getWindowUpdater().add(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
showPrevPage()
|
showPrevPage()
|
||||||
{
|
{
|
||||||
assert !notifications.isEmpty();
|
assert !notifications.isEmpty();
|
||||||
fetchPage(null, notifications.get(0).id);
|
if (fetchPage(null, notifications.get(0).id))
|
||||||
if (notifications.size() < ROW_COUNT) showLatestPage();
|
{
|
||||||
display.showNotifications(notifications);
|
if (notifications.size() < ROW_COUNT) showLatestPage();
|
||||||
|
display.showNotifications(notifications);
|
||||||
|
showingLatest = false;
|
||||||
|
primaire.getWindowUpdater().remove(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void
|
public void
|
||||||
@ -118,41 +146,51 @@ NotificationsWindow extends JFrame {
|
|||||||
{
|
{
|
||||||
assert !notifications.isEmpty();
|
assert !notifications.isEmpty();
|
||||||
int last = notifications.size() - 1;
|
int last = notifications.size() - 1;
|
||||||
fetchPage(notifications.get(last).id, null);
|
if (fetchPage(notifications.get(last).id, null))
|
||||||
display.showNotifications(notifications);
|
{
|
||||||
|
display.showNotifications(notifications);
|
||||||
|
showingLatest = false;
|
||||||
|
primaire.getWindowUpdater().remove(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// - -%- -
|
// - -%- -
|
||||||
|
|
||||||
private void
|
private boolean
|
||||||
fetchPage(String maxId, String minId)
|
fetchPage(String maxId, String minId)
|
||||||
{
|
{
|
||||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
api.getNotifications(
|
class Handler implements RequestListener {
|
||||||
ROW_COUNT, maxId, minId,
|
|
||||||
new RequestListener() {
|
|
||||||
|
|
||||||
public void
|
boolean
|
||||||
connectionFailed(IOException eIo)
|
succeeded = false;
|
||||||
{
|
|
||||||
eIo.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
// -=%=-
|
||||||
requestFailed(int httpCode, Tree<String> json)
|
|
||||||
{
|
|
||||||
System.err.println(httpCode + json.get("error").value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
public void
|
||||||
requestSucceeded(Tree<String> json)
|
connectionFailed(IOException eIo)
|
||||||
{
|
{
|
||||||
displayEntity(json);
|
eIo.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
public void
|
||||||
|
requestFailed(int httpCode, Tree<String> json)
|
||||||
|
{
|
||||||
|
System.err.println(httpCode + json.get("error").value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
requestSucceeded(Tree<String> json)
|
||||||
|
{
|
||||||
|
readEntity(json);
|
||||||
|
succeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Handler handler = new Handler();
|
||||||
|
api.getNotifications(ROW_COUNT, maxId, minId, handler);
|
||||||
display.setCursor(null);
|
display.setCursor(null);
|
||||||
repaint();
|
repaint();
|
||||||
|
return handler.succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---%-@-%---
|
// ---%-@-%---
|
||||||
|
@ -55,6 +55,9 @@ implements ActionListener {
|
|||||||
private MastodonApi
|
private MastodonApi
|
||||||
api;
|
api;
|
||||||
|
|
||||||
|
private WindowUpdater
|
||||||
|
windowUpdater;
|
||||||
|
|
||||||
private TimelinePage
|
private TimelinePage
|
||||||
page;
|
page;
|
||||||
|
|
||||||
@ -78,6 +81,9 @@ implements ActionListener {
|
|||||||
private JMenuItem
|
private JMenuItem
|
||||||
flipToNewestPost;
|
flipToNewestPost;
|
||||||
|
|
||||||
|
private boolean
|
||||||
|
showingLatest;
|
||||||
|
|
||||||
// - -%- -
|
// - -%- -
|
||||||
|
|
||||||
private static final int
|
private static final int
|
||||||
@ -158,10 +164,14 @@ implements ActionListener {
|
|||||||
public void
|
public void
|
||||||
refresh()
|
refresh()
|
||||||
{
|
{
|
||||||
assert page.posts != null;
|
String firstId = null;
|
||||||
assert page.posts.size() != 0;
|
if (!showingLatest)
|
||||||
Tree<String> first = page.posts.get(0);
|
{
|
||||||
String firstId = first.get("id").value;
|
assert page.posts != null;
|
||||||
|
assert page.posts.size() != 0;
|
||||||
|
Tree<String> first = page.posts.get(0);
|
||||||
|
firstId = first.get("id").value;
|
||||||
|
}
|
||||||
|
|
||||||
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
api.getTimelinePage(
|
api.getTimelinePage(
|
||||||
@ -241,6 +251,8 @@ implements ActionListener {
|
|||||||
requestSucceeded(Tree<String> json)
|
requestSucceeded(Tree<String> json)
|
||||||
{
|
{
|
||||||
readEntity(json);
|
readEntity(json);
|
||||||
|
showingLatest = true;
|
||||||
|
windowUpdater.add(TimelineWindow.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -294,6 +306,8 @@ implements ActionListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
readEntity(json);
|
readEntity(json);
|
||||||
|
showingLatest = false;
|
||||||
|
windowUpdater.remove(TimelineWindow.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -345,6 +359,8 @@ implements ActionListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
readEntity(json);
|
readEntity(json);
|
||||||
|
showingLatest = false;
|
||||||
|
windowUpdater.remove(TimelineWindow.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -639,6 +655,7 @@ implements ActionListener {
|
|||||||
{
|
{
|
||||||
this.primaire = primaire;
|
this.primaire = primaire;
|
||||||
this.api = primaire.getMastodonApi();
|
this.api = primaire.getMastodonApi();
|
||||||
|
this.windowUpdater = primaire.getWindowUpdater();
|
||||||
|
|
||||||
getContentPane().setPreferredSize(new Dimension(320, 460));
|
getContentPane().setPreferredSize(new Dimension(320, 460));
|
||||||
pack();
|
pack();
|
||||||
|
@ -1,230 +0,0 @@
|
|||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.io.IOException;
|
|
||||||
import cafe.biskuteri.hinoki.Tree;
|
|
||||||
import javax.sound.sampled.AudioSystem;
|
|
||||||
import javax.sound.sampled.Clip;
|
|
||||||
import javax.sound.sampled.AudioInputStream;
|
|
||||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
class
|
|
||||||
TimelineWindowUpdater {
|
|
||||||
|
|
||||||
private JKomasto
|
|
||||||
primaire;
|
|
||||||
|
|
||||||
private MastodonApi
|
|
||||||
api;
|
|
||||||
|
|
||||||
// - -%- -
|
|
||||||
|
|
||||||
private List<TimelineWindow>
|
|
||||||
timelineUpdatees;
|
|
||||||
|
|
||||||
private List<NotificationsWindow>
|
|
||||||
notificationUpdatees;
|
|
||||||
|
|
||||||
private StringBuilder
|
|
||||||
event, data;
|
|
||||||
|
|
||||||
private Clip
|
|
||||||
notificationSound;
|
|
||||||
|
|
||||||
// - -%- -
|
|
||||||
|
|
||||||
private Thread
|
|
||||||
spublic,
|
|
||||||
user;
|
|
||||||
|
|
||||||
// ---%-@-%---
|
|
||||||
|
|
||||||
public void
|
|
||||||
addWindow(TimelineWindow updatee)
|
|
||||||
{
|
|
||||||
timelineUpdatees.add(updatee);
|
|
||||||
|
|
||||||
Connection c = new Connection();
|
|
||||||
c.type = updatee.getTimelineType();
|
|
||||||
switch (c.type)
|
|
||||||
{
|
|
||||||
case FEDERATED:
|
|
||||||
case LOCAL:
|
|
||||||
if (spublic != null) return;
|
|
||||||
spublic = new Thread(c);
|
|
||||||
spublic.start();
|
|
||||||
break;
|
|
||||||
case HOME:
|
|
||||||
if (user != null) return;
|
|
||||||
user = new Thread(c);
|
|
||||||
user.start();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
addWindow(NotificationsWindow updatee)
|
|
||||||
{
|
|
||||||
notificationUpdatees.add(updatee);
|
|
||||||
|
|
||||||
Connection c = new Connection();
|
|
||||||
c.type = TimelineType.HOME;
|
|
||||||
if (user != null) return;
|
|
||||||
user = new Thread(c);
|
|
||||||
user.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
removeWindow(TimelineWindow updatee)
|
|
||||||
{
|
|
||||||
timelineUpdatees.remove(updatee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
removeWindow(NotificationsWindow updatee)
|
|
||||||
{
|
|
||||||
notificationUpdatees.remove(updatee);
|
|
||||||
}
|
|
||||||
|
|
||||||
// - -%- -
|
|
||||||
|
|
||||||
private void
|
|
||||||
handle(TimelineType type, String event, String data)
|
|
||||||
{
|
|
||||||
assert !data.isEmpty();
|
|
||||||
if (event.isEmpty()) return;
|
|
||||||
|
|
||||||
boolean newPost = event.equals("update");
|
|
||||||
boolean newNotif = event.equals("notification");
|
|
||||||
if (!(newPost || newNotif)) return;
|
|
||||||
|
|
||||||
for (TimelineWindow updatee: filter(type)) {
|
|
||||||
updatee.showLatestPage();
|
|
||||||
}
|
|
||||||
for (NotificationsWindow updatee: notificationUpdatees) {
|
|
||||||
updatee.showLatestPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newNotif)
|
|
||||||
{
|
|
||||||
notificationSound.setFramePosition(0);
|
|
||||||
notificationSound.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TimelineWindow>
|
|
||||||
filter(TimelineType type)
|
|
||||||
{
|
|
||||||
List<TimelineWindow> returnee = new ArrayList<>();
|
|
||||||
for (TimelineWindow updatee: timelineUpdatees)
|
|
||||||
if (updatee.getTimelineType() == type)
|
|
||||||
returnee.add(updatee);
|
|
||||||
return returnee;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---%-@-%---
|
|
||||||
|
|
||||||
private class
|
|
||||||
Connection
|
|
||||||
implements Runnable, ServerSideEventsListener {
|
|
||||||
|
|
||||||
private TimelineType
|
|
||||||
type;
|
|
||||||
|
|
||||||
// -=-
|
|
||||||
|
|
||||||
private StringBuilder
|
|
||||||
event, data;
|
|
||||||
|
|
||||||
// -=%=-
|
|
||||||
|
|
||||||
public void
|
|
||||||
run()
|
|
||||||
{
|
|
||||||
event = new StringBuilder();
|
|
||||||
data = new StringBuilder();
|
|
||||||
api.monitorTimeline(type, this);
|
|
||||||
// monitorTimeline should not return
|
|
||||||
// until the connection is closed.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
lineReceived(String line)
|
|
||||||
{
|
|
||||||
if (line.startsWith(":")) return;
|
|
||||||
|
|
||||||
if (line.isEmpty())
|
|
||||||
{
|
|
||||||
handle(type, event.toString(), data.toString());
|
|
||||||
event.delete(0, event.length());
|
|
||||||
data.delete(0, event.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("data: "))
|
|
||||||
data.append(line.substring("data: ".length()));
|
|
||||||
|
|
||||||
if (line.startsWith("event: "))
|
|
||||||
event.append(line.substring("event: ".length()));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Note that I utterly ignore https://html.spec.whatwg.org
|
|
||||||
* /multipage/server-sent-events.html#dispatchMessage.
|
|
||||||
* That is because I am not a browser.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
connectionFailed(IOException eIo)
|
|
||||||
{
|
|
||||||
// sais pas dois-je faire..
|
|
||||||
eIo.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
requestFailed(int httpCode, Tree<String> json)
|
|
||||||
{
|
|
||||||
// mo shiranu
|
|
||||||
System.err.println(httpCode + ", " + json.get("error").value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---%-@-%---
|
|
||||||
|
|
||||||
TimelineWindowUpdater(JKomasto primaire)
|
|
||||||
{
|
|
||||||
this.primaire = primaire;
|
|
||||||
this.api = primaire.getMastodonApi();
|
|
||||||
|
|
||||||
this.timelineUpdatees = new ArrayList<>();
|
|
||||||
this.notificationUpdatees = new ArrayList<>();
|
|
||||||
|
|
||||||
loadNotificationSound();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
loadNotificationSound()
|
|
||||||
{
|
|
||||||
URL url = getClass().getResource("KDE_Dialog_Appear.wav");
|
|
||||||
try {
|
|
||||||
Clip clip = AudioSystem.getClip();
|
|
||||||
clip.open(AudioSystem.getAudioInputStream(url));
|
|
||||||
notificationSound = clip;
|
|
||||||
}
|
|
||||||
catch (LineUnavailableException eLu) {
|
|
||||||
assert false;
|
|
||||||
}
|
|
||||||
catch (UnsupportedAudioFileException eUa) {
|
|
||||||
assert false;
|
|
||||||
}
|
|
||||||
catch (IOException eIo) {
|
|
||||||
assert false;
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException eIa) {
|
|
||||||
assert false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
275
WindowUpdater.java
Normal file
275
WindowUpdater.java
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.io.IOException;
|
||||||
|
import cafe.biskuteri.hinoki.Tree;
|
||||||
|
import javax.sound.sampled.AudioSystem;
|
||||||
|
import javax.sound.sampled.Clip;
|
||||||
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
class
|
||||||
|
WindowUpdater {
|
||||||
|
|
||||||
|
private JKomasto
|
||||||
|
primaire;
|
||||||
|
|
||||||
|
private MastodonApi
|
||||||
|
api;
|
||||||
|
|
||||||
|
// - -%- -
|
||||||
|
|
||||||
|
private List<TimelineWindow>
|
||||||
|
timelineWindows;
|
||||||
|
|
||||||
|
private List<NotificationsWindow>
|
||||||
|
notificationWindows;
|
||||||
|
|
||||||
|
private Clip
|
||||||
|
notificationSound;
|
||||||
|
|
||||||
|
private Connection
|
||||||
|
publicConn,
|
||||||
|
userConn;
|
||||||
|
|
||||||
|
// ---%-@-%---
|
||||||
|
|
||||||
|
public void
|
||||||
|
add(TimelineWindow updatee)
|
||||||
|
{
|
||||||
|
if (timelineWindows.contains(updatee)) return;
|
||||||
|
|
||||||
|
timelineWindows.add(updatee);
|
||||||
|
publicConn.reevaluate();
|
||||||
|
userConn.reevaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
add(NotificationsWindow updatee)
|
||||||
|
{
|
||||||
|
if (notificationWindows.contains(updatee)) return;
|
||||||
|
|
||||||
|
notificationWindows.add(updatee);
|
||||||
|
userConn.reevaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
remove(TimelineWindow updatee)
|
||||||
|
{
|
||||||
|
timelineWindows.remove(updatee);
|
||||||
|
publicConn.reevaluate();
|
||||||
|
userConn.reevaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
remove(NotificationsWindow updatee)
|
||||||
|
{
|
||||||
|
notificationWindows.remove(updatee);
|
||||||
|
userConn.reevaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---%-@-%---
|
||||||
|
|
||||||
|
private class
|
||||||
|
Connection
|
||||||
|
implements Runnable, ServerSideEventsListener {
|
||||||
|
|
||||||
|
private TimelineType
|
||||||
|
type;
|
||||||
|
|
||||||
|
// -=-
|
||||||
|
|
||||||
|
private Thread
|
||||||
|
thread;
|
||||||
|
|
||||||
|
private StringBuilder
|
||||||
|
event, data;
|
||||||
|
|
||||||
|
// -=%=-
|
||||||
|
|
||||||
|
public void
|
||||||
|
restart()
|
||||||
|
{
|
||||||
|
if (thread != null) stop();
|
||||||
|
thread = new Thread(this);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
stop()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
thread.interrupt();
|
||||||
|
thread.join();
|
||||||
|
thread = null;
|
||||||
|
}
|
||||||
|
catch (InterruptedException eIt) {
|
||||||
|
assert false;
|
||||||
|
// Who would do that to us..
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
reevaluate()
|
||||||
|
{
|
||||||
|
boolean hasUpdatee = false;
|
||||||
|
|
||||||
|
for (NotificationsWindow updatee: notificationWindows)
|
||||||
|
if (responsibleFor(updatee)) hasUpdatee = true;
|
||||||
|
|
||||||
|
for (TimelineWindow updatee: timelineWindows)
|
||||||
|
if (responsibleFor(updatee)) hasUpdatee = true;
|
||||||
|
|
||||||
|
if (!hasUpdatee && thread != null) stop();
|
||||||
|
if (hasUpdatee && thread == null) restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -=-
|
||||||
|
|
||||||
|
private boolean
|
||||||
|
responsibleFor(TimelineWindow updatee)
|
||||||
|
{
|
||||||
|
return type == updatee.getTimelineType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean
|
||||||
|
responsibleFor(NotificationsWindow updatee)
|
||||||
|
{
|
||||||
|
return type == TimelineType.HOME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
run()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
event = new StringBuilder();
|
||||||
|
data = new StringBuilder();
|
||||||
|
api.monitorTimeline(type, this);
|
||||||
|
// monitorTimeline should not return
|
||||||
|
// until the connection is closed.
|
||||||
|
}
|
||||||
|
catch (InterruptedException eIt) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
lineReceived(String line)
|
||||||
|
{
|
||||||
|
if (line.startsWith(":")) return;
|
||||||
|
|
||||||
|
if (line.isEmpty())
|
||||||
|
{
|
||||||
|
handle(event.toString(), data.toString());
|
||||||
|
event.delete(0, event.length());
|
||||||
|
data.delete(0, event.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("data: "))
|
||||||
|
data.append(line.substring("data: ".length()));
|
||||||
|
|
||||||
|
if (line.startsWith("event: "))
|
||||||
|
event.append(line.substring("event: ".length()));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that I ignore https://html.spec.whatwg.org
|
||||||
|
* /multipage/server-sent-events.html#dispatchMessage.
|
||||||
|
* That is because I am not a browser.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private void
|
||||||
|
handle(String event, String data)
|
||||||
|
{
|
||||||
|
assert !data.isEmpty();
|
||||||
|
if (event.isEmpty()) return;
|
||||||
|
|
||||||
|
boolean newPost = event.equals("update");
|
||||||
|
boolean newNotif = event.equals("notification");
|
||||||
|
if (!(newPost || newNotif)) return;
|
||||||
|
|
||||||
|
if (newNotif)
|
||||||
|
{
|
||||||
|
notificationSound.setFramePosition(0);
|
||||||
|
notificationSound.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TimelineWindow updatee: timelineWindows)
|
||||||
|
{
|
||||||
|
if (!responsibleFor(updatee)) continue;
|
||||||
|
updatee.refresh();
|
||||||
|
/*
|
||||||
|
* (悪) Note that we're in a separate thread,
|
||||||
|
* and our windows aren't thread-safe. We could
|
||||||
|
* probably make them a bit bananas asking
|
||||||
|
* for a refresh while they're in the middle
|
||||||
|
* of one. Could we add mutexes?
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
for (NotificationsWindow updatee: notificationWindows)
|
||||||
|
{
|
||||||
|
if (!responsibleFor(updatee)) continue;
|
||||||
|
updatee.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
connectionFailed(IOException eIo)
|
||||||
|
{
|
||||||
|
// sais pas dois-je faire..
|
||||||
|
eIo.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void
|
||||||
|
requestFailed(int httpCode, Tree<String> json)
|
||||||
|
{
|
||||||
|
// mo shiranu
|
||||||
|
System.err.println(httpCode + ", " + json.get("error").value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---%-@-%---
|
||||||
|
|
||||||
|
WindowUpdater(JKomasto primaire)
|
||||||
|
{
|
||||||
|
this.primaire = primaire;
|
||||||
|
this.api = primaire.getMastodonApi();
|
||||||
|
|
||||||
|
this.timelineWindows = new ArrayList<>();
|
||||||
|
this.notificationWindows = new ArrayList<>();
|
||||||
|
|
||||||
|
publicConn = new Connection();
|
||||||
|
publicConn.type = TimelineType.FEDERATED;
|
||||||
|
|
||||||
|
userConn = new Connection();
|
||||||
|
userConn.type = TimelineType.HOME;
|
||||||
|
|
||||||
|
loadNotificationSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
loadNotificationSound()
|
||||||
|
{
|
||||||
|
URL url = getClass().getResource("KDE_Dialog_Appear.wav");
|
||||||
|
try {
|
||||||
|
Clip clip = AudioSystem.getClip();
|
||||||
|
clip.open(AudioSystem.getAudioInputStream(url));
|
||||||
|
notificationSound = clip;
|
||||||
|
}
|
||||||
|
catch (LineUnavailableException eLu) {
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
catch (UnsupportedAudioFileException eUa) {
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
catch (IOException eIo) {
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException eIa) {
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user