1 /* 2 * aTunes 3 * Copyright (C) Alex Aranda, Sylvain Gaudard and contributors 4 * 5 * See http://www.atunes.org/wiki/index.php?title=Contributing for information about contributors 6 * 7 * http://www.atunes.org 8 * http://sourceforge.net/projects/atunes 9 * 10 * This program is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU General Public License 12 * as published by the Free Software Foundation; either version 2 13 * of the License, or (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 */ 20 21 package net.sourceforge.atunes.kernel.modules.podcast; 22 23 import java.awt.event.ActionEvent; 24 import java.awt.event.ActionListener; 25 import java.io.BufferedInputStream; 26 import java.io.File; 27 import java.io.FileNotFoundException; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.net.URLConnection; 32 import java.nio.channels.Channels; 33 import java.nio.channels.FileChannel; 34 import java.nio.channels.ReadableByteChannel; 35 import java.util.List; 36 37 import net.sourceforge.atunes.kernel.BackgroundWorker; 38 import net.sourceforge.atunes.model.IBackgroundWorkerCallback; 39 import net.sourceforge.atunes.model.IDialogFactory; 40 import net.sourceforge.atunes.model.IIconFactory; 41 import net.sourceforge.atunes.model.ILookAndFeelManager; 42 import net.sourceforge.atunes.model.INetworkHandler; 43 import net.sourceforge.atunes.model.IPodcastFeedEntry; 44 import net.sourceforge.atunes.model.IProgressDialog; 45 import net.sourceforge.atunes.model.ITable; 46 import net.sourceforge.atunes.utils.ClosingUtils; 47 import net.sourceforge.atunes.utils.I18nUtils; 48 import net.sourceforge.atunes.utils.Logger; 49 50 /** 51 * The podcast feed entry downloader downloads podcast feed entries from the 52 * internet to a local file. 53 */ 54 public class PodcastFeedEntryDownloader extends 55 BackgroundWorker<Boolean, DownloadPodcastFeedEntryIntermediateResult> { 56 57 private IPodcastFeedEntry podcastFeedEntry; 58 /* 59 * Additional Bean properties 60 */ 61 private volatile long totalBytes; 62 private volatile long byteProgress; 63 private volatile int percentage; 64 private volatile boolean failed; 65 66 private ITable navigationTable; 67 68 private PodcastFeedHandler podcastFeedHandler; 69 70 private INetworkHandler networkHandler; 71 72 private IDialogFactory dialogFactory; 73 74 private IIconFactory rssMediumIcon; 75 76 private ILookAndFeelManager lookAndFeelManager; 77 78 private IProgressDialog dialog; 79 80 /** 81 * @param lookAndFeelManager 82 */ setLookAndFeelManager(ILookAndFeelManager lookAndFeelManager)83 public void setLookAndFeelManager(ILookAndFeelManager lookAndFeelManager) { 84 this.lookAndFeelManager = lookAndFeelManager; 85 } 86 87 /** 88 * @param rssMediumIcon 89 */ setRssMediumIcon(IIconFactory rssMediumIcon)90 public void setRssMediumIcon(IIconFactory rssMediumIcon) { 91 this.rssMediumIcon = rssMediumIcon; 92 } 93 94 /** 95 * @param dialogFactory 96 */ setDialogFactory(IDialogFactory dialogFactory)97 public void setDialogFactory(IDialogFactory dialogFactory) { 98 this.dialogFactory = dialogFactory; 99 } 100 101 /** 102 * @param navigationTable 103 */ setNavigationTable(ITable navigationTable)104 public void setNavigationTable(ITable navigationTable) { 105 this.navigationTable = navigationTable; 106 } 107 108 /** 109 * @param podcastFeedHandler 110 */ setPodcastFeedHandler(PodcastFeedHandler podcastFeedHandler)111 public void setPodcastFeedHandler(PodcastFeedHandler podcastFeedHandler) { 112 this.podcastFeedHandler = podcastFeedHandler; 113 } 114 115 /** 116 * @param networkHandler 117 */ setNetworkHandler(INetworkHandler networkHandler)118 public void setNetworkHandler(INetworkHandler networkHandler) { 119 this.networkHandler = networkHandler; 120 } 121 122 /** 123 * Downloads entry 124 * 125 * @param podcastFeedEntry 126 * @param callback 127 */ download(IPodcastFeedEntry podcastFeedEntry, IBackgroundWorkerCallback<Boolean> callback)128 public void download(IPodcastFeedEntry podcastFeedEntry, 129 IBackgroundWorkerCallback<Boolean> callback) { 130 this.podcastFeedEntry = podcastFeedEntry; 131 execute(callback); 132 } 133 134 @Override before()135 protected void before() { 136 dialog = this.dialogFactory.newDialog("transferDialog", 137 IProgressDialog.class); 138 dialog.setTitle(I18nUtils.getString("PODCAST_FEED_ENTRY_DOWNLOAD")); 139 dialog.setIcon(this.rssMediumIcon.getIcon(this.lookAndFeelManager 140 .getCurrentLookAndFeel().getPaintForSpecialControls())); 141 dialog.addCancelButtonActionListener(new ActionListener() { 142 @Override 143 public void actionPerformed(final ActionEvent e) { 144 podcastFeedHandler.cancelDownloading(podcastFeedEntry, dialog, 145 PodcastFeedEntryDownloader.this); 146 } 147 }); 148 dialog.setInfoText(podcastFeedEntry.getTitle()); 149 dialog.showDialog(); 150 } 151 152 @Override doInBackground()153 protected Boolean doInBackground() { 154 Logger.info("Downloading PodcastEntry: ", podcastFeedEntry.getUrl()); 155 156 InputStream in = null; 157 158 String podcastFeedEntryFileName = podcastFeedHandler 159 .getDownloadPath(podcastFeedEntry); 160 Logger.info("Downloading to: ", podcastFeedEntryFileName); 161 File localFile = new File(podcastFeedEntryFileName); 162 163 ReadableByteChannel readChannel = null; 164 FileChannel writeChannel = null; 165 try { 166 writeChannel = new FileOutputStream(localFile).getChannel(); 167 URLConnection conn = networkHandler.getConnection(podcastFeedEntry 168 .getUrl()); 169 in = new BufferedInputStream(conn.getInputStream()); 170 setTotalBytes(conn.getContentLength()); 171 172 readChannel = Channels.newChannel(in); 173 174 int iterations = 0; 175 long totalTransferred = 0; 176 while (readChannel.isOpen()) { 177 long transferred = writeChannel.transferFrom(readChannel, 178 totalTransferred, 4 * 4096); 179 totalTransferred += transferred; 180 if (iterations % 50 == 0) { 181 setByteProgress(totalTransferred); 182 } 183 } 184 185 return !isCancelled(); 186 } catch (FileNotFoundException e) { 187 Logger.info("file not found"); 188 setFailed(true); 189 return false; 190 } catch (IOException e) { 191 Logger.info("Connection to ", podcastFeedEntry.getUrl(), " failed"); 192 setFailed(true); 193 return false; 194 } finally { 195 ClosingUtils.close(readChannel); 196 ClosingUtils.close(writeChannel); 197 ClosingUtils.close(in); 198 } 199 } 200 201 @Override whileWorking( List<DownloadPodcastFeedEntryIntermediateResult> chunks)202 protected void whileWorking( 203 List<DownloadPodcastFeedEntryIntermediateResult> chunks) { 204 for (DownloadPodcastFeedEntryIntermediateResult chunk : chunks) { 205 if (chunk.isFailed()) { 206 podcastFeedHandler.cancelDownloading(podcastFeedEntry, dialog, 207 this); 208 } else if (chunk.getPercentage() == 100) { 209 dialog.hideDialog(); 210 } else { 211 dialog.setCurrentProgress(chunk.getByteProgress()); 212 dialog.setProgressBarValue(chunk.getPercentage()); 213 dialog.setTotalProgress(chunk.getTotalBytes()); 214 } 215 } 216 } 217 218 /** 219 * Sets the total bytes. 220 * 221 * @param totalBytes 222 * the new total bytes 223 */ 224 setTotalBytes(long totalBytes)225 private void setTotalBytes(long totalBytes) { 226 this.totalBytes = totalBytes; 227 publish(); 228 } 229 230 /** 231 * Sets the byte progress. 232 * 233 * @param byteProgress 234 * the new byte progress 235 */ 236 setByteProgress(long byteProgress)237 private void setByteProgress(long byteProgress) { 238 this.byteProgress = byteProgress; 239 // we want to update progress on byteProgress change 240 this.percentage = (int) (((double) byteProgress / (double) totalBytes) * 100); 241 publish(); 242 } 243 244 /** 245 * Sets the failed. 246 * 247 * @param failed 248 * the new failed 249 */ 250 setFailed(boolean failed)251 private void setFailed(boolean failed) { 252 this.failed = failed; 253 publish(); 254 } 255 publish()256 private void publish() { 257 publish(new DownloadPodcastFeedEntryIntermediateResult(this.percentage, 258 this.byteProgress, this.totalBytes, this.failed)); 259 } 260 261 /** 262 * Gets the total bytes. 263 * 264 * @return the total bytes 265 */ getTotalBytes()266 public long getTotalBytes() { 267 return totalBytes; 268 } 269 270 @Override done(final Boolean result)271 protected void done(final Boolean result) { 272 if (!isCancelled() && result) { 273 Logger.info("Download of " + podcastFeedEntry.getUrl() 274 + " finished."); 275 podcastFeedEntry.setDownloaded(true); 276 navigationTable.repaint(); 277 } 278 } 279 280 /** 281 * Gets the podcast feed entry. 282 * 283 * @return the podcast feed entry 284 */ getPodcastFeedEntry()285 public IPodcastFeedEntry getPodcastFeedEntry() { 286 return podcastFeedEntry; 287 } 288 } 289