· 6 years ago · Feb 17, 2020, 12:34 AM
1package ser321.assign2.lindquis;
2
3import javax.swing.*;
4import java.io.*;
5import java.nio.file.Paths;
6import java.nio.charset.Charset;
7import javax.sound.sampled.*;
8import java.beans.*;
9import java.net.*;
10import javax.swing.tree.*;
11import javax.swing.event.*;
12import javax.swing.text.html.*;
13import javax.swing.filechooser.*;
14import java.awt.event.*;
15import java.awt.*;
16import java.util.*;
17import java.lang.Runtime;
18import java.net.http.HttpClient;
19import java.net.http.HttpRequest;
20import java.net.http.HttpRequest.BodyPublishers;
21import java.net.http.HttpResponse;
22import java.net.http.HttpResponse.BodyHandlers;
23import java.net.URLConnection;
24import java.time.Duration;
25
26import org.json.JSONTokener;
27import org.json.JSONObject;
28import org.json.JSONArray;
29
30/**
31/**
32 * Copyright 2020 Tim Lindquist,
33 *
34 * Licensed under the Apache License, Version 2.0 (the "License");
35 * you may not use this file except in compliance with the License.
36 * You may obtain a copy of the License at
37 *
38 * http://www.apache.org/licenses/LICENSE-2.0
39 *
40 * Unless required by applicable law or agreed to in writing, software
41 * distributed under the License is distributed on an "AS IS" BASIS,
42 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43 * See the License for the specific language governing permissions and
44 * limitations under the License.
45 *
46 * Purpose: MediaLibraryApp instructor sample for solving Spring 2020 ser321 assignments.
47 * This problem provides for browsing and managing information about
48 * music albums. It uses a Swing JTree, and JTextField controls to
49 * realize a GUI with a split pane. The left pane contains an expandable
50 * JTree of the media library.
51 * This program is a sample controller for the non-distributed version of
52 * the system.
53 * The right pane contains components that allow viewing, modifying and adding
54 * albums, tracks, and corresponding files.
55 *
56 * @author Tim Lindquist (Tim.Linquist@asu.edu),
57 * Software Engineering, CIDSE, IAFSE, ASU Poly
58 * @version January 2020
59 */
60public class MediaLibraryApp extends MediaLibraryGui implements TreeWillExpandListener, ActionListener, TreeSelectionListener {
61
62 private static final boolean debugOn = true;
63 private static final String cher = "https://lastfm.freetls.fastly.net/i/u/300x300/3b54885952161aaea4ce2965b2db1638.png";
64 private static final String pre = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo&artist=";
65 private String url;
66 private boolean stopPlaying; //shared, but not synchronized with playing thread.
67 private MediaLibrary library;
68 private String lastFMKey;
69 public Album album;
70 public Album selectedAlbum;
71 private MusicLibrary ml;
72
73 public MediaLibraryApp(String author, String authorKey) {
74 super(author);
75 this.lastFMKey = authorKey;
76 library = new MediaLibraryImpl();
77 stopPlaying = false;
78 ml.restoryLibraryFromFile();
79
80 // register this object as an action listener for menu item clicks. This will cause
81 // my actionPerformed method to be called every time the user selects a menuitem.
82 for(int i=0; i<userMenuItems.length; i++){
83 for(int j=0; j<userMenuItems[i].length; j++){
84 userMenuItems[i][j].addActionListener(this);
85 }
86 }
87 // register this object as an action listener for the Search button. This will cause
88 // my actionPerformed method to be called every time the user clicks the Search button
89 searchJButt.addActionListener(this);
90 try{
91 //tree.addTreeWillExpandListener(this); // add if you want to get called with expansion/contract
92 tree.addTreeSelectionListener(this);
93 rebuildTree();
94 }catch (Exception ex){
95 JOptionPane.showMessageDialog(this,"Handling "+
96 " constructor exception: " + ex.getMessage());
97 }
98 try{
99 /*
100 * display an image just to show how the album or artist image can be displayed in the
101 * app's window. setAlbumImage is implemented by MediaLibraryGui class. Call it with a
102 * string url to a png file as obtained from an album search.
103 */
104 setAlbumImage(cher);
105 }catch(Exception ex){
106 System.out.println("unable to open Cher png");
107 }
108 setVisible(true);
109 }
110
111 /**
112 * A method to facilite printing debugging messages during development, but which can be
113 * turned off as desired.
114 *
115 **/
116
117 private void debug(String message) {
118 if (debugOn)
119 System.out.println("debug: "+message);
120 }
121
122 /**
123 * Create and initialize nodes in the JTree of the left pane.
124 * buildInitialTree is called by MediaLibraryGui to initialize the JTree.
125 * Classes that extend MediaLibraryGui should override this method to
126 * perform initialization actions specific to the extended class.
127 * The default functionality is to set base as the label of root.
128 * In your solution, you will probably want to initialize by deserializing
129 * your library and displaying the categories and subcategories in the
130 * tree.
131 * @param root Is the root node of the tree to be initialized.
132 * @param base Is the string that is the root node of the tree.
133 */
134 public void buildInitialTree(DefaultMutableTreeNode root, String base){
135 //set up the context and base name
136 try{
137 root.setUserObject(base);
138 }catch (Exception ex){
139 JOptionPane.showMessageDialog(this,"exception initial tree:"+ex);
140 ex.printStackTrace();
141 }
142 }
143
144 /**
145 *
146 * method to build the JTree of music shown in the left panel of the UI. The
147 * field tree is a JTree as defined and initialized by MediaLibraryGui class.
148 * it is defined to be protected so it can be accessed by extending classes.
149 * This version of the method uses the music library to get the names of
150 * tracks. Your solutions will need to replace this structure with one that
151 * keeps information particular to both Album and Track (two classes Album.java,
152 * and Track.java). Your music library will store and provide access to Album
153 * and Track objects.
154 * This method is provided to demonstrate one way to add nodes to a JTree based
155 * on an underlying storage structure.
156 * See also the methods clearTree, valueChanged, and getSubLabelled defined in this class.
157 **/
158 public void rebuildTree(){
159 tree.removeTreeSelectionListener(this);
160 //tree.removeTreeWillExpandListener(this);
161 DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
162 DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
163 clearTree(root, model);
164 DefaultMutableTreeNode musicNode = new DefaultMutableTreeNode("Music");
165 model.insertNodeInto(musicNode, root, model.getChildCount(root));
166 DefaultMutableTreeNode searchNode = new DefaultMutableTreeNode("Search");
167 model.insertNodeInto(searchNode, root, model.getChildCount(root));
168 // put nodes in the tree for all registered with the library
169 String[] musicList = library.getTitles();
170 for (Album a: ml.musicLibrary){ //iterating over all albums in our library
171 DefaultMutableTreeNode p = new DefaultMutableTreeNode(a);
172 musicNode.add(p);
173 for (Track t : a.tracks){
174 DefaultMutableTreeNode c = new DefaultMutableTreeNode(t);
175 p.add(c);
176 }
177 }
178 //adding a album we are searching for
179 if (album instanceof Album && album != null){
180 DefaultMutableTreeNode n = new DefaultMutableTreeNode(album);
181 searchNode.add(n);
182 for (int i =0; i < album.tracks.size(); i++){
183 DefaultMutableTreeNode t = new DefaultMutableTreeNode(album.tracks.get(i));
184 n.add(t);
185 }
186 }
187 // expand all the nodes in the JTree
188 for(int r =0; r < tree.getRowCount(); r++){
189 tree.expandRow(r);
190 }
191 tree.addTreeSelectionListener(this);
192 //tree.addTreeWillExpandListener(this);
193 //setVisible(true);
194 }
195
196 private void clearTree(DefaultMutableTreeNode root, DefaultTreeModel model){
197 try{
198 DefaultMutableTreeNode next = null;
199 int subs = model.getChildCount(root);
200 for(int k=subs-1; k>=0; k--){
201 next = (DefaultMutableTreeNode)model.getChild(root,k);
202 debug("removing node labelled:"+(String)next.getUserObject());
203 model.removeNodeFromParent(next);
204 }
205 }catch (Exception ex) {
206 System.out.println("Exception while trying to clear tree:");
207 ex.printStackTrace();
208 }
209 }
210
211 private DefaultMutableTreeNode getSubLabelled(DefaultMutableTreeNode root,
212 String label){
213 DefaultMutableTreeNode ret = null;
214 DefaultMutableTreeNode next = null;
215 boolean found = false;
216 for(Enumeration<TreeNode> e = root.children();
217 e.hasMoreElements();){
218 next = (DefaultMutableTreeNode)e.nextElement();
219 debug("sub with label: "+(String)next.getUserObject());
220 if (((String)next.getUserObject()).equals(label)){
221 debug("found sub with label: "+label);
222 found = true;
223 break;
224 }
225 }
226 if(found)
227 ret = next;
228 else
229 ret = null;
230 return (DefaultMutableTreeNode)ret;
231 }
232
233 public void treeWillCollapse(TreeExpansionEvent tee) {
234 debug("In treeWillCollapse with path: "+tee.getPath());
235 tree.setSelectionPath(tee.getPath());
236 }
237
238 public void treeWillExpand(TreeExpansionEvent tee) {
239 debug("In treeWillExpand with path: "+tee.getPath());
240 //DefaultMutableTreeNode dmtn =
241 // (DefaultMutableTreeNode)tee.getPath().getLastPathComponent();
242 //System.out.println("will expand node: "+dmtn.getUserObject()+
243 // " whose path is: "+tee.getPath());
244 }
245
246 public void valueChanged(TreeSelectionEvent e) {
247 try{
248 tree.removeTreeSelectionListener(this);
249 DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
250 if(node!=null){
251 if (node.getUserObject() instanceof Track){
252 DefaultMutableTreeNode p = (DefaultMutableTreeNode) node.getParent();
253 //Lines for Album
254 albumJTF.setText(((Album) p.getUserObject()).albumName);
255 authorJTF.setText(((Album) p.getUserObject()).artist);
256 setAlbumImage(((Album) p.getUserObject()).image);
257 summaryJTA.setText(((Album) p.getUserObject()).summaryToString() + "");
258
259 //Lines for Track
260 trackJTF.setText(((Track) node.getUserObject()).trackName + "");
261 //rankJTF.setText(((Track) node.getUserObject()).rankOrder + "");
262 timeJTF.setText(((Track) node.getUserObject()).duration + "");
263
264 selectedAlbum = (Album) p.getUserObject();
265 }
266 else if (node.getUserObject() instanceof Album){
267 albumJTF.setText(((Album) node.getUserObject()).albumName);
268 authorJTF.setText(((Album) node.getUserObject()).artist);
269 setAlbumImage(((Album) node.getUserObject()).image);
270 timeJTF.setText(((Album) node.getUserObject()).runtime + "");
271 summaryJTA.setText(((Album) node.getUserObject()).summary);
272 trackJTF.setText(" ");
273 //rankJTF.setText(" ");
274
275 selectedAlbum = (Album) node.getUserObject();
276 }
277 }
278 else{
279 return;
280 }
281 }
282 catch (Exception ex){
283 ex.printStackTrace();
284 }
285 tree.addTreeSelectionListener(this);
286 }
287
288 public void actionPerformed(ActionEvent e) {
289 tree.removeTreeSelectionListener(this);
290 if(e.getActionCommand().equals("Exit")) {
291 System.exit(0);
292 }
293 else if(e.getActionCommand().equals("Save")) {
294 boolean savRes = ml.saveLibraryToFile();
295 System.out.println("Save "+((savRes)?"successful":"not implemented"));
296 }
297 else if(e.getActionCommand().equals("Restore")) {
298 boolean resRes = ml.restoryLibraryFromFile();
299 rebuildTree();
300 System.out.println("Restore "+((resRes)?"successful":"not implemented"));
301 }
302 else if(e.getActionCommand().equals("AlbumAdd")) {
303 if (album != null) {
304 ml.addAlbum(album);
305 album = null;
306 }
307 }
308 else if(e.getActionCommand().equals("TrackAdd")) {
309 int typeInd = genreJCB.getSelectedIndex();
310 MediaDescription aMD = new MediaDescription(trackJTF.getText().trim(),
311 authorJTF.getText().trim(),
312 albumJTF.getText().trim(),
313 fileNameJTF.getText().trim());
314 library.add(aMD);
315 rebuildTree();
316 /*
317 JFileChooser chooser = new JFileChooser();
318 chooser.setCurrentDirectory(
319 new File(System.getProperty("user.dir")));
320 FileNameExtensionFilter filter = new FileNameExtensionFilter(
321 "mp3 files", "mp3");
322 chooser.setFileFilter(filter);
323 int returnVal = chooser.showOpenDialog(this);
324 if(returnVal == JFileChooser.APPROVE_OPTION) {
325 debug("You chose to open the file: " +
326 chooser.getSelectedFile().getName());
327 }
328 */
329 }
330 else if(e.getActionCommand().equals("Search")) {
331 String searchReqURL = pre+artistSearchJTF.getText().trim()+"&album="+albumSearchJTF.getText().trim()+
332 "&api_key="+lastFMKey+"&format=json";
333 System.out.println("calling fetchAsyncURL with url: "+searchReqURL);
334 String json = fetchAsyncURL(searchReqURL);
335 album = ml.getFMAlbum(json);
336 if (album == null){
337 return;
338 }
339
340 fileNameJTF.setText("placeholder.mp3");
341 albumJTF.setText(album.albumName);
342 authorJTF.setText(album.artist);
343 setAlbumImage(album.image);
344 summaryJTA.setText(album.summary);
345 trackJTF.setText(album.tracks.get(0).trackName);
346 rebuildTree();
347 }
348 else if(e.getActionCommand().equals("Tree Refresh")) {
349 rebuildTree();
350 }
351 else if(e.getActionCommand().equals("TrackRemove")) {
352 System.out.println("TrackRemove not implemented");
353 }
354 else if(e.getActionCommand().equals("AlbumRemove")) {
355 if (selectedAlbum != null){
356 ml.removeAlbum(selectedAlbum);
357 }
358 }
359 else if(e.getActionCommand().equals("AlbumPlay") || e.getActionCommand().equals("TrackPlay")){
360 try{
361 DefaultMutableTreeNode node = (DefaultMutableTreeNode)
362 tree.getLastSelectedPathComponent();
363 if(node!=null){
364 String nodeLabel = (String)node.getUserObject();
365 MediaDescription md = library.get(nodeLabel);
366 String fileName = md.fileName;
367 String path = "file://"+System.getProperty("user.dir")+
368 "/MediaFiles/" + fileName;
369 this.playMedia(path, md.title);
370 }
371 }catch(Exception ex){
372 System.out.println("Execption trying to play media:");
373 ex.printStackTrace();
374 }
375 }
376 tree.addTreeSelectionListener(this);
377 rebuildTree();
378 return;
379 }
380
381 /**
382 *
383 * A method to do asynchronous url request printing the result to System.out
384 * @param aUrl the String indicating the query url for the lastFM api search
385 *
386 **/
387 public String fetchAsyncURL(String aUrl){
388 try{
389 HttpClient client = HttpClient.newHttpClient();
390 HttpRequest request = HttpRequest.newBuilder()
391 .uri(URI.create(aUrl))
392 .timeout(Duration.ofMinutes(1))
393 .build();
394 client.sendAsync(request, BodyHandlers.ofString())
395 .thenApply(HttpResponse::body)
396 .thenAccept(System.out::println)
397 .join();
398 }catch(Exception ex){
399 System.out.println("Exception in fetchAsyncUrl request: "+ex.getMessage());
400 }
401 return null;
402 }
403
404 /**
405 *
406 * a method to make a web request. Note that this method will block execution
407 * for up to 20 seconds while the request is being satisfied. Better to use a
408 * non-blocking request.
409 * @param aUrl the String indicating the query url for the lastFM api search
410 * @return the String result of the http request.
411 *
412 **/
413 public String fetchURL(String aUrl) {
414 StringBuilder sb = new StringBuilder();
415 URLConnection conn = null;
416 InputStreamReader in = null;
417 try {
418 URL url = new URL(aUrl);
419 conn = url.openConnection();
420 if (conn != null)
421 conn.setReadTimeout(20 * 1000); // timeout in 20 seconds
422 if (conn != null && conn.getInputStream() != null) {
423 in = new InputStreamReader(conn.getInputStream(),
424 Charset.defaultCharset());
425 BufferedReader br = new BufferedReader(in);
426 if (br != null) {
427 int ch;
428 // read the next character until end of reader
429 while ((ch = br.read()) != -1) {
430 sb.append((char)ch);
431 }
432 br.close();
433 }
434 }
435 in.close();
436 } catch (Exception ex) {
437 System.out.println("Exception in url request:"+ ex.getMessage());
438 }
439 return sb.toString();
440 }
441
442 public boolean sezToStop(){
443 return stopPlaying;
444 }
445
446 public static void main(String args[]) {
447 String name = "first.last";
448 String key = "use-your-last.fm-key-here";
449 if (args.length >= 2){
450 //System.out.println("java -cp classes:lib/json.lib ser321.assign2.lindquist."+
451 // "MediaLibraryApp \"Lindquist Music Library\" lastFM-Key");
452 name = args[0];
453 key = args[1];
454 }
455 try{
456 //System.out.println("calling constructor name "+name);
457 MediaLibraryApp mla = new MediaLibraryApp(name,key);
458 }catch (Exception ex){
459 ex.printStackTrace();
460 }
461 }
462}