*** lyricwiki.cc.audacious	2019-10-15 11:39:20.547866535 -0500
--- lyricwiki.cc	2019-10-19 13:16:57.812628473 -0500
***************
*** 19,24 ****
--- 19,25 ----
   */
  
  #include <glib.h>
+ #include <glib/gstdio.h>
  #include <string.h>
  
  #include <QApplication>
*************** typedef struct {
*** 50,55 ****
--- 51,59 ----
      String filename; /* of song file */
      String title, artist;
      String uri; /* URI we are trying to retrieve */
+     String local_filename; /* JWT:CALCULATED LOCAL FILENAME TO SAVE LYRICS TO */
+     int startlyrics;       /* JWT:OFFSET IN LYRICS WINDOW WHERE LYRIC TEXT ACTUALLY STARTS */
+     bool ok2save;          /* JWT:SET TO TRUE IF GOT LYRICS FROM LYRICWIKI (LOCAL FILE DOESN'T EXIST) */
  } LyricsState;
  
  static LyricsState state;
*************** static String scrape_uri_from_lyricwiki_
*** 260,266 ****
                  {
                      // Convert normal lyrics link to edit page link
                      char * slash = strrchr (lyric, '/');
!                     if (slash && ! strstr (slash, "lyrics.wikia.com"))
                          uri = String (str_printf ("https://lyrics.fandom.com/index.php?"
                           "action=edit&title=%s", slash + 1));
                      else
--- 264,271 ----
                  {
                      // Convert normal lyrics link to edit page link
                      char * slash = strrchr (lyric, '/');
!                     // if (slash && ! strstr (slash, "lyrics.wikia.com")) // JWT:FIXME?! - CHGD. TO MATCH GTK SIDE!:
!                     if (slash))
                          uri = String (str_printf ("https://lyrics.fandom.com/index.php?"
                           "action=edit&title=%s", slash + 1));
                      else
*************** static void get_lyrics_step_3 (const cha
*** 303,314 ****
  
      if (! lyrics)
      {
!         update_lyrics_window (_("Error"), nullptr,
!          str_printf (_("Unable to parse %s"), uri));
          return;
      }
  
      update_lyrics_window (state.title, state.artist, lyrics);
  }
  
  static void get_lyrics_step_2 (const char * uri1, const Index<char> & buf, void *)
--- 308,322 ----
  
      if (! lyrics)
      {
!         update_lyrics_window (_("No lyrics Found"),
!                 (const char *) str_concat ({"Title: ", (const char *) state.title, "\nArtist: ",
!                         (const char *) state.artist}),
!                 str_printf (_("Unable to parse %s"), uri));
          return;
      }
  
      update_lyrics_window (state.title, state.artist, lyrics);
+     state.ok2save = true;
  }
  
  static void get_lyrics_step_2 (const char * uri1, const Index<char> & buf, void *)
*************** static void get_lyrics_step_1 ()
*** 348,354 ****
  {
      if (! state.artist || ! state.title)
      {
!         update_lyrics_window (_("Error"), nullptr, _("Missing song metadata"));
          return;
      }
  
--- 356,362 ----
  {
      if (! state.artist || ! state.title)
      {
!         update_lyrics_window (_("Error"), nullptr, _("Missing title and/or artist"));
          return;
      }
  
*************** static void get_lyrics_step_1 ()
*** 363,370 ****
--- 371,442 ----
      vfs_async_file_get_contents (state.uri, get_lyrics_step_2, nullptr);
  }
  
+ static void get_lyrics_step_0 (const char * uri, const Index<char> & buf, void *)
+ {
+     if (! buf.len ())
+     {
+         update_lyrics_window (_("Error"), nullptr,
+          str_printf (_("Unable to fetch file %s"), uri));
+         return;
+     }
+ 
+     StringBuf nullterminated_buf = str_copy (buf.begin (), buf.len ());
+     update_lyrics_window (state.title, state.artist, (const char *) nullterminated_buf);
+ 
+     /* JWT:ALLOW 'EM TO EDIT LYRICWIKI, EVEN IF LYRICS ARE LOCAL, IF THEY HAVE BOTH REQUIRED FIELDS: */
+     if (state.artist && state.title)
+     {
+         StringBuf title_buf = str_copy (state.title);
+         str_replace_char (title_buf, ' ', '_');
+         title_buf = str_encode_percent (title_buf, -1);
+         StringBuf artist_buf = str_copy (state.artist);
+         str_replace_char (artist_buf, ' ', '_');
+         artist_buf = str_encode_percent (artist_buf, -1);
+         state.uri = String (str_printf ("https://lyrics.fandom.com/index.php?action=edit&title=%s:%s",
+                 (const char *) artist_buf, (const char *) title_buf));
+     }
+ }
+ 
  static QTextEdit * textedit;
  
+ static void save_lyrics_locally ()
+ {
+     if (state.local_filename)
+     {
+         QString lyrics = textedit->toPlainText ();
+         if (! lyrics.isNull() && ! lyrics.isEmpty())
+         {
+             if (state.startlyrics > 0)
+                 lyrics.remove(0, state.startlyrics);
+ 
+             int sz = lyrics.length ();
+             if (sz > 0)
+             {
+                 if (strstr ((const char *) state.local_filename, "/lyrics/"))
+                 {
+                     GStatBuf statbuf;
+                     StringBuf path = filename_get_parent ((const char *) state.local_filename);
+                     if (g_stat ((const char *) path, & statbuf)
+                             && g_mkdir ((const char *) path, 0755))
+                     {
+                         AUDERR ("e:Could not create missing lyrics directory (%s)!\n", (const char *) path);
+                         return;
+                     }
+                 }
+                 VFSFile file (state.local_filename, "w");
+                 if (file)
+                 {
+                     if (file.fwrite (lyrics.toUtf8().constData(), 1, sz) == sz)
+                         AUDINFO ("i:Successfully saved %d bytes of lyrics locally to (%s).\n", sz,
+                                 (const char *) state.local_filename);
+ 
+                     state.ok2save = false;
+                 }
+             }
+         }
+     }
+ }
+ 
  static void update_lyrics_window (const char * title, const char * artist, const char * lyrics)
  {
      if (! textedit)
*************** static void update_lyrics_window (const
*** 381,386 ****
--- 453,460 ----
      }
  
      cursor.insertHtml ("<br><br>");
+     QString prelyrics = textedit->toPlainText ();
+     state.startlyrics = prelyrics.length ();
      cursor.insertText (lyrics);
  }
  
*************** static void lyricwiki_playback_began ()
*** 388,402 ****
  {
      /* FIXME: cancel previous VFS requests (not possible with current API) */
  
      state.filename = aud_drct_get_filename ();
  
      Tuple tuple = aud_drct_get_tuple ();
      state.title = tuple.get_str (Tuple::Title);
      state.artist = tuple.get_str (Tuple::Artist);
  
!     state.uri = String ();
  
!     get_lyrics_step_1 ();
  }
  
  static void lw_cleanup (QObject * object = nullptr)
--- 462,602 ----
  {
      /* FIXME: cancel previous VFS requests (not possible with current API) */
  
+     bool found_lyricfile = false;
+     GStatBuf statbuf;
+     String lyricStr = String ("");
+     StringBuf path = StringBuf ();
+ 
      state.filename = aud_drct_get_filename ();
+     state.uri = String ();
+ 
+     state.local_filename = String ("");
+     if (! strncmp ((const char *) state.filename, "file://", 7))  // JWT:WE'RE A LOCAL FILE, CHECK FOR CORRESPONDING LYRICS FILE:
+     {
+         /* JWT: EXTRACT JUST THE "NAME" PART TO USE TO NAME THE LYRICS FILE: */
+         const char * slash = state.filename ? strrchr (state.filename, '/') : nullptr;
+         const char * base = slash ? slash + 1 : nullptr;
+ 
+         if (base && base[0])
+         {
+             /* JWT:FIRST CHECK LOCAL DIRECTORY FOR A LYRICS FILE MATCHING FILE-NAME: */
+             const char * dot = strrchr (base, '.');
+             int ln = (dot && ! strstr (dot, ".cue?")) ? (dot - base) : -1;  // SET TO FULL LENGTH(-1) IF NO EXTENSION OR NOT A CUESHEET.
+             path = filename_get_parent (uri_to_filename (state.filename));
+             lyricStr = String (str_concat ({path, "/", str_decode_percent (base, ln), ".lrc"}));
+             found_lyricfile = ! (g_stat ((const char *) lyricStr, & statbuf));
+             state.local_filename = lyricStr;
+ 
+             if (! found_lyricfile)
+             {
+                 /* JWT:LOCAL LYRIC FILE NOT FOUND, SO CHECK THE GLOBAL CONFIG PATH FOR A MATCHING LYRICS FILE: */
+                 lyricStr = String (str_concat ({aud_get_path (AudPath::UserDir), "/lyrics/",
+                         str_decode_percent (base, ln), ".lrc"}));
+                 found_lyricfile = ! (g_stat ((const char *) lyricStr, & statbuf));
+             }
+         }
+     }
  
      Tuple tuple = aud_drct_get_tuple ();
      state.title = tuple.get_str (Tuple::Title);
      state.artist = tuple.get_str (Tuple::Artist);
+     state.ok2save = false;
  
!     if (found_lyricfile)  // JWT:WE HAVE LYRICS STORED IN A LOCAL FILE MATCHING FILE NAME!:
!     {
!         AUDINFO ("i:Local lyric file found (%s).\n", (const char *) lyricStr);
!         vfs_async_file_get_contents (lyricStr, get_lyrics_step_0, nullptr);
!     }
!     else  // NO LOCAL LYRICS FILE FOUND, SO CHECK FOR LYRIC FILE MATCHING TITLE:
!     {
!         if (state.title)
!         {
!             /* JWT:MANY STREAMS & SOME FILES FORMAT THE TITLE FIELD AS:
!                "[Artist: ]<artist> - [Title: ]<title> [<other-stuff>?]".  IF SO, THEN PARSE OUT THE
!                ARTIST AND TITLE COMPONENTS FROM THE TITLE FOR SEARCHING LYRICWIKI:
!             */
!             const char * ttlstart = (const char *) state.title;
!             int ttllen = strlen (ttlstart);  // MAKE SURE WE DON'T OVERRUN (THIS IS C)! ;^)
!             if (ttllen > 8 && ! strcmp_nocase (ttlstart, "Artist:", 7))
!                 ttlstart += 8;
! 
!             if (ttllen > 0)  // MAKE SURE WE DON'T OVERRUN!
!             {
!                 const char * ttloffset = ttlstart ? strstr (ttlstart, " - ") : nullptr;
!                 if (ttloffset)
!                 {
!                     state.artist = String (str_copy (ttlstart, (ttloffset-ttlstart)));
!                     ttloffset += 3;
!                     ttllen -= 3;
!                     if (ttllen > 7 && ! strcmp_nocase (ttloffset, "Title:", 6))
!                         ttloffset += 7;
  
!                     ttllen = strlen (ttloffset);
!                     if (ttllen > 0)
!                     {
!                         const char * ttlend = strstr (ttloffset, " - ");
!                         if (ttlend)
!                             state.title = String (str_copy (ttloffset, ttlend-ttloffset));
!                         else
!                         {
!                             auto split = str_list_to_index (ttloffset, "|/");
!                             for (auto & str : split)
!                             {
!                                 int ttllen_1 = strlen (str) - 1;  // "CHOMP" ANY TRAILING SPACES:
!                                 while (ttllen_1 >= 0 && str[ttllen_1] == ' ')
!                                     ttllen_1--;
! 
!                                 if (ttllen_1 >= 0)
!                                 {
!                                     StringBuf titleBuf = str_copy (str);
!                                     titleBuf.resize (ttllen_1+1);
!                                     state.title = String (titleBuf);
!                                 }
!                                 break;
!                             }
!                         }
!                     }
!                 }
!                 if (! state.local_filename || ! state.local_filename[0])
!                 {
!                     /* JWT:NO LOCAL LYRIC FILE, SO TRY SEARCH FOR LYRIC FILE BY TITLE: */
!                     StringBuf titleBuf = str_copy (state.title);
!                     /* DON'T %-ENCODE SPACES BY CONVERTING TO A "LEGAL" CHAR. NOT (LIKELY) IN FILE-NAMES/TITLES: */
!                     str_replace_char (titleBuf, ' ', '~');
!                     titleBuf = str_encode_percent ((const char *) titleBuf, -1);
!                     str_replace_char (titleBuf, '~', ' ');  // (THEN CONVERT 'EM BACK TO SPACES)
!                     if (path)
!                     {
!                         /* ENTRY IS A LOCAL FILE, SO FIRST CHECK DIRECTORY THE FILE IS IN: */
!                         lyricStr = String (str_concat ({path, "/", titleBuf, ".lrc"}));
!                         found_lyricfile = ! (g_stat ((const char *) lyricStr, & statbuf));
!                         state.local_filename = lyricStr;
!                         if (found_lyricfile)
!                         {
!                             AUDINFO ("i:Local lyric file found by title (%s).\n", (const char *) lyricStr);
!                             vfs_async_file_get_contents (lyricStr, get_lyrics_step_0, nullptr);
!                             return;
!                         }
!                     }
!                     /* OTHERWISE (STREAM, ETC.), CHECK THE GLOBAL CONFIG PATH FOR LYRICS FILE MATCHING TITLE: */
!                     lyricStr = String (str_concat ({aud_get_path (AudPath::UserDir),
!                             "/lyrics/", titleBuf, ".lrc"}));
!                     found_lyricfile = ! (g_stat ((const char *) lyricStr, & statbuf));
!                     state.local_filename = lyricStr;
!                     if (found_lyricfile)
!                     {
!                         AUDINFO ("i:Global lyric file found by title (%s).\n", (const char *) lyricStr);
!                         vfs_async_file_get_contents (lyricStr, get_lyrics_step_0, nullptr);
!                         return;
!                     }
!                 }
!             }
!         }
!         /* IF HERE, NO LOCAL LYRICS FILE BY FILENAME OR TITLE, SEARCH LYRICWIKI: */
!         AUDINFO ("i:No Local lyric file found, try fetching from lyricwiki...\n");
!         get_lyrics_step_1 ();
!     }
!     lyricStr = String ();
  }
  
  static void lw_cleanup (QObject * object = nullptr)
*************** static void lw_cleanup (QObject * object
*** 405,410 ****
--- 605,611 ----
      state.title = String ();
      state.artist = String ();
      state.uri = String ();
+     state.local_filename = String ();
  
      hook_dissociate ("tuple change", (HookFunction) lyricwiki_playback_began);
      hook_dissociate ("playback ready", (HookFunction) lyricwiki_playback_began);
*************** void * LyricWikiQt::get_qt_widget ()
*** 434,448 ****
  
  void TextEdit::contextMenuEvent (QContextMenuEvent * event)
  {
-     if (! state.uri)
-         return QTextEdit::contextMenuEvent (event);
- 
      QMenu * menu = createStandardContextMenu ();
      menu->addSeparator ();
!     QAction * edit = menu->addAction (_("Edit lyrics ..."));
!     QObject::connect (edit, & QAction::triggered, [] () {
!         QDesktopServices::openUrl (QUrl ((const char *) state.uri));
!     });
      menu->exec (event->globalPos ());
      delete menu;
  }
--- 635,657 ----
  
  void TextEdit::contextMenuEvent (QContextMenuEvent * event)
  {
      QMenu * menu = createStandardContextMenu ();
      menu->addSeparator ();
! 
!     if (state.uri)
!     {
!         QAction * edit = menu->addAction (_("Edit Lyricwiki"));
!         QObject::connect (edit, & QAction::triggered, [] () {
!             QDesktopServices::openUrl (QUrl ((const char *) state.uri));
!         });
!     }
!     if (state.ok2save)
!     {
!         QAction * save_button = menu->addAction (_("Save Locally"));
!         QObject::connect (save_button, & QAction::triggered, [] () {
!             save_lyrics_locally ();
!         });
!     }
      menu->exec (event->globalPos ());
      delete menu;
  }
