11package fr .totetmatt .blueskygephi ;
22
33import fr .totetmatt .blueskygephi .atproto .AtClient ;
4+ import fr .totetmatt .blueskygephi .atproto .AtIdentity ;
5+ import fr .totetmatt .blueskygephi .atproto .AtProtoException ;
46import fr .totetmatt .blueskygephi .atproto .response .common .Identity ;
57import java .awt .Color ;
8+ import java .awt .EventQueue ;
69import java .util .ArrayList ;
710import java .util .HashSet ;
811import java .util .List ;
1114import java .util .concurrent .ExecutorService ;
1215import java .util .concurrent .Executors ;
1316import java .util .function .LongConsumer ;
17+ import java .util .logging .Level ;
1418import java .util .logging .Logger ;
1519import java .util .prefs .Preferences ;
1620import java .util .stream .Collectors ;
1721import java .util .stream .Stream ;
22+ import javax .swing .Icon ;
23+ import javax .swing .UIManager ;
1824import org .gephi .graph .api .Edge ;
1925import org .gephi .graph .api .Graph ;
2026import org .gephi .graph .api .GraphController ;
2127import org .gephi .graph .api .GraphModel ;
2228import org .gephi .graph .api .Node ;
23- import org .gephi .project .api .Project ;
2429import org .gephi .project .api .ProjectController ;
30+ import org .gephi .project .api .Workspace ;
2531import org .gephi .utils .progress .Progress ;
2632import org .gephi .utils .progress .ProgressTicket ;
2733import org .gephi .utils .progress .ProgressTicketProvider ;
28- import org .openide .util . Exceptions ;
34+ import org .openide .awt . NotificationDisplayer ;
2935import org .openide .util .Lookup ;
3036import org .openide .util .NbPreferences ;
3137import org .openide .util .lookup .ServiceProvider ;
@@ -65,12 +71,18 @@ public class BlueskyGephi {
6571 public BlueskyGephi () {
6672 }
6773
68- private void ensureProject () {
74+ private void ensureWorkspace () {
6975 ProjectController projectController = Lookup .getDefault ().lookup (ProjectController .class );
70- Project currentProject = projectController . getCurrentProject ();
71- if (currentProject == null ) {
76+ // A workspace can only live inside a project.
77+ if (projectController . getCurrentProject () == null ) {
7278 projectController .newProject ();
7379 }
80+ // A project can exist with no open workspace (e.g. all were closed), and
81+ // the graph model can't be resolved until one is created and opened.
82+ if (projectController .getCurrentWorkspace () == null ) {
83+ Workspace workspace = projectController .newWorkspace (projectController .getCurrentProject ());
84+ projectController .openWorkspace (workspace );
85+ }
7486 }
7587
7688 public boolean connect (String host ,String handle , String password ) {
@@ -85,6 +97,18 @@ public boolean connect(String host,String handle, String password) {
8597 public String getHost (){
8698 return nbPref .get (NBPREF_ATPROTO_HOST ,"bsky.social" );
8799 }
100+
101+ /**
102+ * Determines the account's PDS host from its handle (unauthenticated), so
103+ * the user can auto-fill the host field. This is a network call and must be
104+ * run off the EDT.
105+ *
106+ * @return the resolved PDS host, e.g. {@code shaggymane.us-west.host.bsky.network}.
107+ * @throws AtProtoException if the handle can't be resolved to a PDS.
108+ */
109+ public String resolveHostFromHandle (String handle ) {
110+ return AtIdentity .resolveHost (handle );
111+ }
88112 public String getHandle () {
89113 return nbPref .get (NBPREF_BSKY_HANDLE , "" );
90114 }
@@ -171,6 +195,25 @@ private Edge createEdge(Node source, Node target) {
171195 return edge ;
172196 }
173197
198+ /**
199+ * Reports a failure to the user without the raw NetBeans stack-trace dialog:
200+ * the full technical detail (with stack trace) is logged for debugging, while
201+ * the user sees a concise, non-blocking notification bubble with an
202+ * actionable reason.
203+ */
204+ private void reportError (String title , String detail , Throwable t ) {
205+ logger .log (Level .WARNING , title + " - " + detail , t );
206+ EventQueue .invokeLater (() -> {
207+ Icon icon = UIManager .getIcon ("OptionPane.warningIcon" );
208+ if (icon == null ) {
209+ icon = new javax .swing .ImageIcon (
210+ new java .awt .image .BufferedImage (16 , 16 , java .awt .image .BufferedImage .TYPE_INT_ARGB ));
211+ }
212+ NotificationDisplayer .getDefault ().notify (title , icon , detail , null ,
213+ NotificationDisplayer .Priority .HIGH );
214+ });
215+ }
216+
174217 private void fetchFollowerFollowsFromActor (String actor , List <String > listInit , boolean isFollowsActive , boolean isFollowersActive , boolean isDeepSearch ) {
175218 // Run on a bounded background pool to keep the Gephi UI responsive.
176219 executor .submit (new FetchTask (actor , listInit , isFollowsActive , isFollowersActive , isDeepSearch ));
@@ -283,9 +326,17 @@ private void process(String actor, boolean isDeepSearch, Optional<Integer> limit
283326 }
284327 }, onWaiting );
285328 }
329+ } catch (AtProtoException e ) {
330+ // Expected failure mode (auth, rate limit, bad handle, network):
331+ // report a clear reason plus the server's own response, not a stack trace.
332+ if (!cancelled ) {
333+ reportError ("Bluesky: could not fetch \" " + actor + "\" " , e .getUserMessageWithContent (), e );
334+ }
286335 } catch (Exception e ) {
287336 if (!cancelled ) {
288- Exceptions .printStackTrace (e );
337+ reportError ("Bluesky: could not fetch \" " + actor + "\" " ,
338+ "Unexpected error: " + e .getClass ().getSimpleName ()
339+ + (e .getMessage () != null ? " - " + e .getMessage () : "" ), e );
289340 }
290341 }
291342 }
@@ -368,11 +419,12 @@ private void initGraphTable() {
368419 }
369420
370421 public void fetchFollowerFollowsFromActors (List <String > actors , boolean isFollowsActive , boolean isFollowersActive , boolean isBlocksActive ) {
422+ // Guarantee a workspace exists before touching the graph model.
423+ ensureWorkspace ();
371424 if (client == null ) {
372425 logger .warning ("Not connected to Bluesky. Please connect before fetching." );
373426 return ;
374427 }
375- ensureProject ();
376428 graphModel = Lookup .getDefault ().lookup (GraphController .class ).getGraphModel ();
377429 initGraphTable ();
378430 actors .stream ()
@@ -382,11 +434,12 @@ public void fetchFollowerFollowsFromActors(List<String> actors, boolean isFollow
382434 }
383435
384436 public void fetchFollowerFollowsFromActors (List <String > actors ) {
437+ // Guarantee a workspace exists before touching the graph model.
438+ ensureWorkspace ();
385439 if (client == null ) {
386440 logger .warning ("Not connected to Bluesky. Please connect before fetching." );
387441 return ;
388442 }
389- ensureProject ();
390443 graphModel = Lookup .getDefault ().lookup (GraphController .class ).getGraphModel ();
391444 initGraphTable ();
392445
@@ -407,10 +460,18 @@ public void fetchFollowerFollowsFromActors(List<String> actors) {
407460 // Resolving a list is itself several paged network calls, so do it
408461 // off the EDT instead of blocking the UI thread.
409462 executor .submit (() -> {
410- List <String > listActor = listIds .stream ()
411- .flatMap (this ::manageList )
412- .collect (Collectors .toList ());
413- fetchFollowerFollowsFromActor (null , listActor , getIsFollowsActive (), getIsFollowersActive (), getIsDeepSearch ());
463+ try {
464+ List <String > listActor = listIds .stream ()
465+ .flatMap (this ::manageList )
466+ .collect (Collectors .toList ());
467+ fetchFollowerFollowsFromActor (null , listActor , getIsFollowsActive (), getIsFollowersActive (), getIsDeepSearch ());
468+ } catch (AtProtoException e ) {
469+ reportError ("Bluesky: could not resolve list" , e .getUserMessageWithContent (), e );
470+ } catch (Exception e ) {
471+ reportError ("Bluesky: could not resolve list" ,
472+ "Unexpected error: " + e .getClass ().getSimpleName ()
473+ + (e .getMessage () != null ? " - " + e .getMessage () : "" ), e );
474+ }
414475 });
415476 }
416477 }
0 commit comments