biskuteri-cafe-JKomasto2/WindowUpdater.java
Snowyfox 775faa0bcc Fixed window updater thread issue (seemingly).
Better support for multithreading.
Fixed notifications window refresh bug.
Fixed autoview window bug (temporary scrollpane doesn't reset scroll position)
2022-05-06 16:34:22 -04:00

298 lines
6.7 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 void
add(TimelineWindow updatee)
{
if (!timelineWindows.contains(updatee))
timelineWindows.add(updatee);
publicConn.reevaluate();
userConn.reevaluate();
}
public void
add(NotificationsWindow updatee)
{
if (!notificationWindows.contains(updatee))
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 boolean
stopping;
private StringBuilder
event, data;
// -=%=-
public void
start()
{
stopping = false;
thread = new Thread(this);
try
{
synchronized (thread)
{
thread.start();
thread.wait();
}
}
catch (InterruptedException eIt)
{
assert false;
}
}
public void
stop()
{
stopping = true;
thread.interrupt();
try
{
thread.join();
}
catch (InterruptedException eIt)
{
assert false;
}
thread = null;
/*
* That thread should notice it is interrupted
* promptly, and close.
*/
}
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) 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.
}
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();
}
}
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;
}
}
}