mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 04:54:50 +01:00
e6fea4c061
(Before this, JKomasto and sometimes the Mastodon web client would get '411 Record Not Found' when submitting the same text after deleting and redrafting. Presumably the Mastodon server caches both whether an idempotency key was fulfilled and which post it leads to, and for some reason it looks up the second and fails.)
318 lines
7.1 KiB
Java
318 lines
7.1 KiB
Java
|
|
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 synchronized void
|
|
add(TimelineWindow updatee)
|
|
{
|
|
if (!timelineWindows.contains(updatee))
|
|
timelineWindows.add(updatee);
|
|
|
|
publicConn.reevaluate();
|
|
userConn.reevaluate();
|
|
}
|
|
|
|
public synchronized void
|
|
add(NotificationsWindow updatee)
|
|
{
|
|
if (!notificationWindows.contains(updatee))
|
|
notificationWindows.add(updatee);
|
|
|
|
userConn.reevaluate();
|
|
}
|
|
|
|
public synchronized void
|
|
remove(TimelineWindow updatee)
|
|
{
|
|
timelineWindows.remove(updatee);
|
|
publicConn.reevaluate();
|
|
userConn.reevaluate();
|
|
}
|
|
|
|
public synchronized void
|
|
remove(NotificationsWindow updatee)
|
|
{
|
|
notificationWindows.remove(updatee);
|
|
userConn.reevaluate();
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
public static void
|
|
printStackTrace(Thread thread)
|
|
{
|
|
for (StackTraceElement e: thread.getStackTrace())
|
|
System.err.println(e);
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
private class
|
|
Connection
|
|
implements Runnable, ServerSideEventsListener {
|
|
|
|
private TimelineType
|
|
type;
|
|
|
|
// -=-
|
|
|
|
private Thread
|
|
thread;
|
|
|
|
private boolean
|
|
stopping;
|
|
|
|
private StringBuilder
|
|
event, data;
|
|
|
|
// -=%=-
|
|
|
|
public void
|
|
start()
|
|
{
|
|
stopping = false;
|
|
thread = new Thread(this);
|
|
thread.setDaemon(true);
|
|
try
|
|
{
|
|
synchronized (thread)
|
|
{
|
|
thread.start();
|
|
thread.wait();
|
|
}
|
|
}
|
|
catch (InterruptedException eIt)
|
|
{
|
|
assert false;
|
|
}
|
|
}
|
|
|
|
public void
|
|
stop()
|
|
{
|
|
stopping = true;
|
|
thread.interrupt();
|
|
try
|
|
{
|
|
thread.join(3000);
|
|
/*
|
|
* That thread should notice it is
|
|
* interrupted ppromptly, and close.
|
|
*/
|
|
if (thread.isAlive()) printStackTrace(thread);
|
|
}
|
|
catch (InterruptedException eIt)
|
|
{
|
|
assert false;
|
|
}
|
|
thread = null;
|
|
}
|
|
|
|
public void
|
|
reevaluate()
|
|
{
|
|
boolean hasUpdatee = false;
|
|
|
|
for (NotificationsWindow w: notificationWindows)
|
|
if (responsibleFor(w)) hasUpdatee = true;
|
|
|
|
for (TimelineWindow w: timelineWindows)
|
|
if (responsibleFor(w)) hasUpdatee = true;
|
|
|
|
if (!hasUpdatee && thread != null) stop();
|
|
if (hasUpdatee && thread == null) start();
|
|
}
|
|
|
|
// -=-
|
|
|
|
public void
|
|
run()
|
|
{
|
|
synchronized (thread)
|
|
{
|
|
thread.notifyAll();
|
|
}
|
|
event = new StringBuilder();
|
|
data = new StringBuilder();
|
|
api.monitorTimeline(type, this);
|
|
// monitorTimeline should not return until
|
|
// the connection is closed, or this thread
|
|
// is interrupted.
|
|
|
|
System.err.println(
|
|
"Stopped monitoring."
|
|
+ thread + " " + Thread.currentThread()
|
|
);
|
|
if (thread == Thread.currentThread()) thread = null;
|
|
/*
|
|
* This isn't thread safe. But I'd like the
|
|
* restart after sleep mode, so.
|
|
*/
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
synchronized (WindowUpdater.this)
|
|
{
|
|
for (TimelineWindow w: timelineWindows)
|
|
{
|
|
if (!responsibleFor(w)) continue;
|
|
w.refresh();
|
|
}
|
|
}
|
|
|
|
synchronized (WindowUpdater.this)
|
|
{
|
|
for (NotificationsWindow w: notificationWindows)
|
|
{
|
|
if (!responsibleFor(w)) continue;
|
|
w.refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean
|
|
responsibleFor(TimelineWindow updatee)
|
|
{
|
|
return type == updatee.getTimelineType();
|
|
}
|
|
|
|
private boolean
|
|
responsibleFor(NotificationsWindow updatee)
|
|
{
|
|
return type == TimelineType.HOME;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
}
|