Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/BlueskyGephi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<groupId>fr.totetmatt</groupId>
<artifactId>bluesky-gephi</artifactId>
<version>0.2.2</version>
<version>0.2.3</version>
<packaging>nbm</packaging>

<name>Bluesky Gephi</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package fr.totetmatt.blueskygephi;

import fr.totetmatt.blueskygephi.atproto.AtClient;
import fr.totetmatt.blueskygephi.atproto.AtIdentity;
import fr.totetmatt.blueskygephi.atproto.AtProtoException;
import fr.totetmatt.blueskygephi.atproto.response.common.Identity;
import java.awt.Color;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand All @@ -11,21 +14,24 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.LongConsumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.Icon;
import javax.swing.UIManager;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.Graph;
import org.gephi.graph.api.GraphController;
import org.gephi.graph.api.GraphModel;
import org.gephi.graph.api.Node;
import org.gephi.project.api.Project;
import org.gephi.project.api.ProjectController;
import org.gephi.project.api.Workspace;
import org.gephi.utils.progress.Progress;
import org.gephi.utils.progress.ProgressTicket;
import org.gephi.utils.progress.ProgressTicketProvider;
import org.openide.util.Exceptions;
import org.openide.awt.NotificationDisplayer;
import org.openide.util.Lookup;
import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
Expand Down Expand Up @@ -65,12 +71,18 @@ public class BlueskyGephi {
public BlueskyGephi() {
}

private void ensureProject() {
private void ensureWorkspace() {
ProjectController projectController = Lookup.getDefault().lookup(ProjectController.class);
Project currentProject = projectController.getCurrentProject();
if (currentProject == null) {
// A workspace can only live inside a project.
if (projectController.getCurrentProject() == null) {
projectController.newProject();
}
// A project can exist with no open workspace (e.g. all were closed), and
// the graph model can't be resolved until one is created and opened.
if (projectController.getCurrentWorkspace() == null) {
Workspace workspace = projectController.newWorkspace(projectController.getCurrentProject());
projectController.openWorkspace(workspace);
}
}

public boolean connect(String host,String handle, String password) {
Expand All @@ -85,6 +97,18 @@ public boolean connect(String host,String handle, String password) {
public String getHost(){
return nbPref.get(NBPREF_ATPROTO_HOST,"bsky.social");
}

/**
* Determines the account's PDS host from its handle (unauthenticated), so
* the user can auto-fill the host field. This is a network call and must be
* run off the EDT.
*
* @return the resolved PDS host, e.g. {@code shaggymane.us-west.host.bsky.network}.
* @throws AtProtoException if the handle can't be resolved to a PDS.
*/
public String resolveHostFromHandle(String handle) {
return AtIdentity.resolveHost(handle);
}
public String getHandle() {
return nbPref.get(NBPREF_BSKY_HANDLE, "");
}
Expand Down Expand Up @@ -171,6 +195,25 @@ private Edge createEdge(Node source, Node target) {
return edge;
}

/**
* Reports a failure to the user without the raw NetBeans stack-trace dialog:
* the full technical detail (with stack trace) is logged for debugging, while
* the user sees a concise, non-blocking notification bubble with an
* actionable reason.
*/
private void reportError(String title, String detail, Throwable t) {
logger.log(Level.WARNING, title + " - " + detail, t);
EventQueue.invokeLater(() -> {
Icon icon = UIManager.getIcon("OptionPane.warningIcon");
if (icon == null) {
icon = new javax.swing.ImageIcon(
new java.awt.image.BufferedImage(16, 16, java.awt.image.BufferedImage.TYPE_INT_ARGB));
}
NotificationDisplayer.getDefault().notify(title, icon, detail, null,
NotificationDisplayer.Priority.HIGH);
});
}

private void fetchFollowerFollowsFromActor(String actor, List<String> listInit, boolean isFollowsActive, boolean isFollowersActive, boolean isDeepSearch) {
// Run on a bounded background pool to keep the Gephi UI responsive.
executor.submit(new FetchTask(actor, listInit, isFollowsActive, isFollowersActive, isDeepSearch));
Expand Down Expand Up @@ -283,9 +326,17 @@ private void process(String actor, boolean isDeepSearch, Optional<Integer> limit
}
}, onWaiting);
}
} catch (AtProtoException e) {
// Expected failure mode (auth, rate limit, bad handle, network):
// report a clear reason plus the server's own response, not a stack trace.
if (!cancelled) {
reportError("Bluesky: could not fetch \"" + actor + "\"", e.getUserMessageWithContent(), e);
}
} catch (Exception e) {
if (!cancelled) {
Exceptions.printStackTrace(e);
reportError("Bluesky: could not fetch \"" + actor + "\"",
"Unexpected error: " + e.getClass().getSimpleName()
+ (e.getMessage() != null ? " - " + e.getMessage() : ""), e);
}
}
}
Expand Down Expand Up @@ -368,11 +419,12 @@ private void initGraphTable() {
}

public void fetchFollowerFollowsFromActors(List<String> actors, boolean isFollowsActive, boolean isFollowersActive, boolean isBlocksActive) {
// Guarantee a workspace exists before touching the graph model.
ensureWorkspace();
if (client == null) {
logger.warning("Not connected to Bluesky. Please connect before fetching.");
return;
}
ensureProject();
graphModel = Lookup.getDefault().lookup(GraphController.class).getGraphModel();
initGraphTable();
actors.stream()
Expand All @@ -382,11 +434,12 @@ public void fetchFollowerFollowsFromActors(List<String> actors, boolean isFollow
}

public void fetchFollowerFollowsFromActors(List<String> actors) {
// Guarantee a workspace exists before touching the graph model.
ensureWorkspace();
if (client == null) {
logger.warning("Not connected to Bluesky. Please connect before fetching.");
return;
}
ensureProject();
graphModel = Lookup.getDefault().lookup(GraphController.class).getGraphModel();
initGraphTable();

Expand All @@ -407,10 +460,18 @@ public void fetchFollowerFollowsFromActors(List<String> actors) {
// Resolving a list is itself several paged network calls, so do it
// off the EDT instead of blocking the UI thread.
executor.submit(() -> {
List<String> listActor = listIds.stream()
.flatMap(this::manageList)
.collect(Collectors.toList());
fetchFollowerFollowsFromActor(null, listActor, getIsFollowsActive(), getIsFollowersActive(), getIsDeepSearch());
try {
List<String> listActor = listIds.stream()
.flatMap(this::manageList)
.collect(Collectors.toList());
fetchFollowerFollowsFromActor(null, listActor, getIsFollowsActive(), getIsFollowersActive(), getIsDeepSearch());
} catch (AtProtoException e) {
reportError("Bluesky: could not resolve list", e.getUserMessageWithContent(), e);
} catch (Exception e) {
reportError("Bluesky: could not resolve list",
"Unexpected error: " + e.getClass().getSimpleName()
+ (e.getMessage() != null ? " - " + e.getMessage() : ""), e);
}
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,21 @@
<Component id="credentialsConnectButton" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="credentialsPasswordLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHandleLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHostLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="credentialsPasswordLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="credentialsHandleField" pref="263" max="32767" attributes="0"/>
<Component id="credentialsPasswordField" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="credentialsHostField" pref="263" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="credentialsCheckButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="credentialsHostLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="43" max="-2" attributes="0"/>
<Component id="credentialsHostField" pref="263" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
Expand All @@ -125,13 +126,14 @@
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="0" pref="9" max="32767" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="credentialsHostLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHostField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHandleLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHandleField" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="credentialsHandleLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHandleField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHostLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsHostField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsCheckButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
Expand Down Expand Up @@ -209,6 +211,19 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="credentialsConnectButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="credentialsCheckButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="fr/totetmatt/blueskygephi/Bundle.properties" key="BlueskyGephiMainPanel.credentialsCheckButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="fr/totetmatt/blueskygephi/Bundle.properties" key="BlueskyGephiMainPanel.credentialsCheckButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="credentialsCheckButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
Expand Down
Loading
Loading