public synchronized void schedule() { Runnable task = new Runnable() { public void run() { LOG.info("Starting scheduled Podcast refresh."); refreshAllChannels(true); LOG.info("Completed scheduled Podcast refresh."); } }; if (scheduledRefresh != null) { scheduledRefresh.cancel(true); } int hoursBetween = settingsService.getPodcastUpdateInterval(); if (hoursBetween == -1) { LOG.info("Automatic Podcast update disabled."); return; } long periodMillis = hoursBetween * 60L * 60L * 1000L; long initialDelayMillis = 5L * 60L * 1000L; scheduledRefresh = scheduledExecutor.scheduleAtFixedRate( task, initialDelayMillis, periodMillis, TimeUnit.MILLISECONDS); Date firstTime = new Date(System.currentTimeMillis() + initialDelayMillis); LOG.info( "Automatic Podcast update scheduled to run every " + hoursBetween + " hour(s), starting at " + firstTime); }
public void execute(JdbcTemplate template) { if (template.queryForInt("select count(*) from version where version = 6") == 0) { LOG.info("Updating database schema to version 6."); template.execute("insert into version values (6)"); } if (!columnExists(template, "last_fm_enabled", "user_settings")) { LOG.info("Database columns 'user_settings.last_fm_*' not found. Creating them."); template.execute( "alter table user_settings add last_fm_enabled boolean default false not null"); template.execute("alter table user_settings add last_fm_username varchar null"); template.execute("alter table user_settings add last_fm_password varchar null"); LOG.info("Database columns 'user_settings.last_fm_*' were added successfully."); } if (!columnExists(template, "transcode_scheme", "user_settings")) { LOG.info("Database column 'user_settings.transcode_scheme' not found. Creating it."); template.execute( "alter table user_settings add transcode_scheme varchar default '" + TranscodeScheme.OFF.name() + "' not null"); LOG.info("Database column 'user_settings.transcode_scheme' was added successfully."); } }
@Override public void execute(JdbcTemplate template) { if (template.queryForInt("select count(*) from version where version = 21") == 0) { LOG.info("Updating database schema to version 21."); template.execute("insert into version values (21)"); } if (!columnExists(template, "year", "album")) { LOG.info("Database column 'album.year' not found. Creating it."); template.execute("alter table album add year int"); LOG.info("Database column 'album.year' was added successfully."); } if (!columnExists(template, "genre", "album")) { LOG.info("Database column 'album.genre' not found. Creating it."); template.execute("alter table album add genre varchar"); LOG.info("Database column 'album.genre' was added successfully."); } if (!tableExists(template, "genre")) { LOG.info("Database table 'genre' not found. Creating it."); template.execute( "create table genre (" + "name varchar not null," + "song_count int not null)"); LOG.info("Database table 'genre' was created successfully."); } if (!columnExists(template, "album_count", "genre")) { LOG.info("Database column 'genre.album_count' not found. Creating it."); template.execute("alter table genre add album_count int default 0 not null"); LOG.info("Database column 'genre.album_count' was added successfully."); } }
private void saveCoverArt(String path, String url) throws Exception { InputStream input = null; HttpClient client = new DefaultHttpClient(); try { HttpConnectionParams.setConnectionTimeout(client.getParams(), 20 * 1000); // 20 seconds HttpConnectionParams.setSoTimeout(client.getParams(), 20 * 1000); // 20 seconds HttpGet method = new HttpGet(url); HttpResponse response = client.execute(method); input = response.getEntity().getContent(); // Attempt to resolve proper suffix. String suffix = "jpg"; if (url.toLowerCase().endsWith(".gif")) { suffix = "gif"; } else if (url.toLowerCase().endsWith(".png")) { suffix = "png"; } // Check permissions. File newCoverFile = new File(path, "cover." + suffix); if (!securityService.isWriteAllowed(newCoverFile)) { throw new Exception("Permission denied: " + StringUtil.toHtml(newCoverFile.getPath())); } // If file exists, create a backup. backup(newCoverFile, new File(path, "cover.backup." + suffix)); // Write file. IOUtils.copy(input, new FileOutputStream(newCoverFile)); MediaFile mediaFile = mediaFileService.getMediaFile(path); // Rename existing cover file if new cover file is not the preferred. try { File coverFile = mediaFileService.getCoverArt(mediaFile); if (coverFile != null) { if (!newCoverFile.equals(coverFile)) { coverFile.renameTo(new File(coverFile.getCanonicalPath() + ".old")); LOG.info("Renamed old image file " + coverFile); } } } catch (Exception x) { LOG.warn("Failed to rename existing cover file.", x); } mediaFileService.refreshMediaFile(mediaFile); } finally { IOUtils.closeQuietly(input); client.getConnectionManager().shutdown(); } }
/** * Returns a number of random songs. * * @param criteria Search criteria. * @return List of random songs. */ public List<MediaFile> getRandomSongs(RandomSearchCriteria criteria) { List<MediaFile> result = new ArrayList<MediaFile>(); IndexReader reader = null; try { reader = createIndexReader(SONG); Searcher searcher = new IndexSearcher(reader); BooleanQuery query = new BooleanQuery(); query.add( new TermQuery(new Term(FIELD_MEDIA_TYPE, MediaFile.MediaType.MUSIC.name().toLowerCase())), BooleanClause.Occur.MUST); if (criteria.getGenre() != null) { String genre = normalizeGenre(criteria.getGenre()); query.add(new TermQuery(new Term(FIELD_GENRE, genre)), BooleanClause.Occur.MUST); } if (criteria.getFromYear() != null || criteria.getToYear() != null) { NumericRangeQuery<Integer> rangeQuery = NumericRangeQuery.newIntRange( FIELD_YEAR, criteria.getFromYear(), criteria.getToYear(), true, true); query.add(rangeQuery, BooleanClause.Occur.MUST); } List<SpanTermQuery> musicFolderQueries = new ArrayList<SpanTermQuery>(); for (MusicFolder musicFolder : criteria.getMusicFolders()) { musicFolderQueries.add( new SpanTermQuery(new Term(FIELD_FOLDER, musicFolder.getPath().getPath()))); } query.add( new SpanOrQuery(musicFolderQueries.toArray(new SpanQuery[musicFolderQueries.size()])), BooleanClause.Occur.MUST); TopDocs topDocs = searcher.search(query, null, Integer.MAX_VALUE); List<ScoreDoc> scoreDocs = Lists.newArrayList(topDocs.scoreDocs); Random random = new Random(System.currentTimeMillis()); while (!scoreDocs.isEmpty() && result.size() < criteria.getCount()) { int index = random.nextInt(scoreDocs.size()); Document doc = searcher.doc(scoreDocs.remove(index).doc); int id = Integer.valueOf(doc.get(FIELD_ID)); try { addIfNotNull(mediaFileService.getMediaFile(id), result); } catch (Exception x) { LOG.warn("Failed to get media file " + id); } } } catch (Throwable x) { LOG.error("Failed to search or random songs.", x); } finally { FileUtil.closeQuietly(reader); } return result; }
private void backup(File newCoverFile, File backup) { if (newCoverFile.exists()) { if (backup.exists()) { backup.delete(); } if (newCoverFile.renameTo(backup)) { LOG.info("Backed up old image file to " + backup); } else { LOG.warn("Failed to create image file backup " + backup); } } }
private synchronized File getImageCacheDirectory(int size) { File dir = new File(SettingsService.getSubsonicHome(), "thumbs"); dir = new File(dir, String.valueOf(size)); if (!dir.exists()) { if (dir.mkdirs()) { LOG.info("Created thumbnail cache " + dir); } else { LOG.error("Failed to create thumbnail cache " + dir); } } return dir; }
public void index(Album album) { try { albumId3Writer.addDocument(ALBUM_ID3.createDocument(album)); } catch (Exception x) { LOG.error("Failed to create search index for " + album, x); } }
public void index(Artist artist, MusicFolder musicFolder) { try { artistId3Writer.addDocument(ARTIST_ID3.createDocument(artist, musicFolder)); } catch (Exception x) { LOG.error("Failed to create search index for " + artist, x); } }
private File getCachedImage(File file, int size) throws IOException { String md5 = DigestUtils.md5Hex(file.getPath()); File cachedImage = new File(getImageCacheDirectory(size), md5 + ".jpeg"); // Is cache missing or obsolete? if (!cachedImage.exists() || FileUtil.lastModified(file) > cachedImage.lastModified()) { InputStream in = null; OutputStream out = null; try { in = getImageInputStream(file); out = new FileOutputStream(cachedImage); BufferedImage image = ImageIO.read(in); if (image == null) { throw new Exception("Unable to decode image."); } image = scale(image, size, size); ImageIO.write(image, "jpeg", out); } catch (Throwable x) { // Delete corrupt (probably empty) thumbnail cache. LOG.warn("Failed to create thumbnail for " + file, x); IOUtils.closeQuietly(out); cachedImage.delete(); throw new IOException("Failed to create thumbnail for " + file + ". " + x.getMessage()); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } } return cachedImage; }
public CacheDao() { File subsonicHome = SettingsService.getSubsonicHome(); File dbDir = new File(subsonicHome, "cache"); dbFile = new File(dbDir, "cache.dat"); if (!dbDir.exists()) { dbDir.mkdirs(); } // if (dbFile.exists()) { // try { // Defragment.defrag(dbFile.getPath()); // }catch (IOException e) { // e.printStackTrace(); // } // } try { openDatabase(dbFile); } catch (Throwable x) { LOG.error("Failed to open " + dbFile + ", deleting it: " + x); dbFile.delete(); openDatabase(dbFile); } }
/** * Creates a transcoded input stream by executing the given command. If <code>in</code> is not * null, data from it is copied to the command. * * @param command The command to execute. * @param in Data to feed to the command. May be <code>null</code>. * @throws IOException If an I/O error occurs. */ public TranscodeInputStream(String[] command, final InputStream in) throws IOException { StringBuffer buf = new StringBuffer("Starting transcoder: "); for (String s : command) { buf.append('[').append(s).append("] "); } LOG.debug(buf); process = Runtime.getRuntime().exec(command); processOutputStream = process.getOutputStream(); processInputStream = process.getInputStream(); // Must read stderr from the process, otherwise it may block. final String name = command[0]; new InputStreamReaderThread(process.getErrorStream(), name, true).start(); // Copy data in a separate thread if (in != null) { new Thread(name + " TranscodedInputStream copy thread") { public void run() { try { IOUtils.copy(in, processOutputStream); } catch (IOException x) { // Intentionally ignored. Will happen if the remote player closes the stream. } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(processOutputStream); } } }.start(); } }
/* * Instantiate MediaFile by path name. Only to be used by file based browser. */ public MediaFile getNonIndexedMediaFile(File file) { int fileId = -Math.abs(file.getAbsolutePath().hashCode()); LOG.debug( "request for non indexed media file " + file.getAbsolutePath() + ", cache as " + fileId); Element element = mediaFileCache.get(fileId); if (element != null) { // Check if cache is up-to-date. MediaFile cachedMediaFile = (MediaFile) element.getObjectValue(); if (cachedMediaFile.lastModified() >= file.lastModified()) { return cachedMediaFile; } } if (!securityService.isReadAllowed(file)) { throw new SecurityException("Access denied to file " + file); } MediaFile mediaFile = new MediaFile(fileId, file); mediaFileCache.put(new Element(fileId, mediaFile)); return mediaFile; }
/** * Used for creating and evolving the database schema. This class implementes the database schema * for Subsonic version 2.7. * * @author Sindre Mehus */ public class Schema27 extends Schema { private static final Logger LOG = Logger.getLogger(Schema27.class); public void execute(JdbcTemplate template) { if (template.queryForInt("select count(*) from version where version = 3") == 0) { LOG.info("Updating database schema to version 3."); template.execute("insert into version values (3)"); LOG.info("Converting database column 'music_file_info.path' to varchar_ignorecase."); template.execute("drop index idx_music_file_info_path"); template.execute("alter table music_file_info alter column path varchar_ignorecase not null"); template.execute("create index idx_music_file_info_path on music_file_info(path)"); LOG.info("Database column 'music_file_info.path' was converted successfully."); } if (!columnExists(template, "bytes_streamed", "user")) { LOG.info( "Database columns 'user.bytes_streamed/downloaded/uploaded' not found. Creating them."); template.execute("alter table user add bytes_streamed bigint default 0 not null"); template.execute("alter table user add bytes_downloaded bigint default 0 not null"); template.execute("alter table user add bytes_uploaded bigint default 0 not null"); LOG.info( "Database columns 'user.bytes_streamed/downloaded/uploaded' were added successfully."); } } }
/** * Used for creating and evolving the database schema. This class implementes the database schema * for Subsonic version 3.4. * * @author Sindre Mehus */ public class Schema34 extends Schema { private static final Logger LOG = Logger.getLogger(Schema34.class); public void execute(JdbcTemplate template) { if (template.queryForInt("select count(*) from version where version = 10") == 0) { LOG.info("Updating database schema to version 10."); template.execute("insert into version values (10)"); } if (!columnExists(template, "ldap_authenticated", "users")) { LOG.info("Database column 'users.ldap_authenticated' not found. Creating it."); template.execute("alter table users add ldap_authenticated boolean default false not null"); LOG.info("Database column 'users.ldap_authenticated' was added successfully."); } if (!columnExists(template, "party_mode_enabled", "users_settings")) { LOG.info("Database column 'users_settings.party_mode_enabled' not found. Creating it."); template.execute( "alter table users_settings add party_mode_enabled boolean default false not null"); LOG.info("Database column 'users_settings.party_mode_enabled' was added successfully."); } } }
private void removeLocks() { for (IndexType indexType : IndexType.values()) { Directory dir = null; try { dir = FSDirectory.open(getIndexDirectory(indexType)); if (IndexWriter.isLocked(dir)) { IndexWriter.unlock(dir); LOG.info("Removed Lucene lock file in " + dir); } } catch (Exception x) { LOG.warn("Failed to remove Lucene lock file in " + dir, x); } finally { FileUtil.closeQuietly(dir); } } }
public class UserService { private static final Logger LOG = Logger.getLogger(UserService.class); private SecurityService securityService; public void init() { CheckUser(); } public void CheckUser() { Runnable runnable = new Runnable() { public void run() { try { LOG.info("Checking User accounts ..."); securityService.checkAccounts(); LOG.info("Checking Done"); } catch (Throwable x) { LOG.error("Failed to check User service: " + x, x); } } }; new Thread(runnable).start(); } public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } }
@Override public void execute(JdbcTemplate template) { if (template.queryForInt("select count(*) from version where version = 28") == 0) { LOG.info("Updating database schema to version 28."); template.execute("insert into version values (28)"); if (!columnExists(template, "only_album_artist_recommendations", "user_settings")) { LOG.info( "Database column 'user_settings.only_album_artist_recommendations' not found. Creating it."); template.execute( "alter table user_settings add only_album_artist_recommendations boolean default true not null"); LOG.info( "Database column 'user_settings.only_album_artist_recommendations' was added successfully."); } } }
/** * Downloads and saves the cover art at the given URL. * * @param path The directory in which to save the image. * @param url The image URL. * @return The error string if something goes wrong, <code>null</code> otherwise. */ public String setCoverArtImage(String path, String url) { try { saveCoverArt(path, url); return null; } catch (Exception x) { LOG.warn("Failed to save cover art for " + path, x); return x.toString(); } }
/** Feed the other end with some dummy data to keep it from reconnecting. */ private void sendDummy(byte[] buf, OutputStream out) throws IOException { try { Thread.sleep(2000); } catch (InterruptedException x) { LOG.warn("Interrupted in sleep.", x); } Arrays.fill(buf, (byte) 0xFF); out.write(buf); out.flush(); }
/** * Used for creating and evolving the database schema. This class implementes the database schema * for Subsonic version 2.5. * * @author Sindre Mehus */ public class Schema25 extends Schema { private static final Logger LOG = Logger.getLogger(Schema25.class); public void execute(JdbcTemplate template) { if (!tableExists(template, "version")) { LOG.info("Database table 'version' not found. Creating it."); template.execute("create table version (version int not null)"); template.execute("insert into version values (1)"); LOG.info("Database table 'version' was created successfully."); } if (!tableExists(template, "role")) { LOG.info("Database table 'role' not found. Creating it."); template.execute( "create table role (" + "id int not null," + "name varchar not null," + "primary key (id))"); template.execute("insert into role values (1, 'admin')"); template.execute("insert into role values (2, 'download')"); template.execute("insert into role values (3, 'upload')"); template.execute("insert into role values (4, 'playlist')"); template.execute("insert into role values (5, 'coverart')"); LOG.info("Database table 'role' was created successfully."); } if (!tableExists(template, "user")) { LOG.info("Database table 'user' not found. Creating it."); template.execute( "create table user (" + "username varchar not null," + "password varchar not null," + "primary key (username))"); template.execute("insert into user values ('admin', 'admin')"); LOG.info("Database table 'user' was created successfully."); } if (!tableExists(template, "user_role")) { LOG.info("Database table 'user_role' not found. Creating it."); template.execute( "create table user_role (" + "username varchar not null," + "role_id int not null," + "primary key (username, role_id)," + "foreign key (username) references user(username)," + "foreign key (role_id) references role(id))"); template.execute("insert into user_role values ('admin', 1)"); template.execute("insert into user_role values ('admin', 2)"); template.execute("insert into user_role values ('admin', 3)"); template.execute("insert into user_role values ('admin', 4)"); template.execute("insert into user_role values ('admin', 5)"); LOG.info("Database table 'user_role' was created successfully."); } } }
public MediaFile getMediaFile(int trackId) { if (!mediaFileCache.isElementInMemory(trackId)) { LOG.debug("media file not in memory, load meta data from db!"); loadMediaFiles(Arrays.asList(trackId)); if (!mediaFileCache.isElementInMemory(trackId)) { // trackId might refer to a playing track/video that since have been removed return null; } } return (MediaFile) mediaFileCache.get(trackId).getValue(); }
public void startIndexing() { try { artistWriter = createIndexWriter(ARTIST); artistId3Writer = createIndexWriter(ARTIST_ID3); albumWriter = createIndexWriter(ALBUM); albumId3Writer = createIndexWriter(ALBUM_ID3); songWriter = createIndexWriter(SONG); } catch (Exception x) { LOG.error("Failed to create search index.", x); } }
private Date parseDate(String s) { for (DateFormat dateFormat : RSS_DATE_FORMATS) { try { return dateFormat.parse(s); } catch (Exception x) { // Ignored. } } LOG.warn("Failed to parse publish date: '" + s + "'."); return null; }
private long getFileLength(TranscodingService.Parameters parameters) { MediaFile file = parameters.getMediaFile(); if (!parameters.isDownsample() && !parameters.isTranscode()) { return file.getFileSize(); } Integer duration = file.getDurationSeconds(); Integer maxBitRate = parameters.getMaxBitRate(); if (duration == null) { LOG.warn("Unknown duration for " + file + ". Unable to estimate transcoded size."); return file.getFileSize(); } if (maxBitRate == null) { LOG.error("Unknown bit rate for " + file + ". Unable to estimate transcoded size."); return file.getFileSize(); } return duration * maxBitRate * 1000L / 8L; }
public void index(MediaFile mediaFile) { try { if (mediaFile.isFile()) { songWriter.addDocument(SONG.createDocument(mediaFile)); } else if (mediaFile.isAlbum()) { albumWriter.addDocument(ALBUM.createDocument(mediaFile)); } else { artistWriter.addDocument(ARTIST.createDocument(mediaFile)); } } catch (Exception x) { LOG.error("Failed to create search index for " + mediaFile, x); } }
/** * Returns a number of random albums, using ID3 tag. * * @param count Number of albums to return. * @param musicFolders Only return albums from these folders. * @return List of random albums. */ public List<Album> getRandomAlbumsId3(int count, List<MusicFolder> musicFolders) { List<Album> result = new ArrayList<Album>(); IndexReader reader = null; try { reader = createIndexReader(ALBUM_ID3); Searcher searcher = new IndexSearcher(reader); List<SpanTermQuery> musicFolderQueries = new ArrayList<SpanTermQuery>(); for (MusicFolder musicFolder : musicFolders) { musicFolderQueries.add( new SpanTermQuery( new Term(FIELD_FOLDER_ID, NumericUtils.intToPrefixCoded(musicFolder.getId())))); } Query query = new SpanOrQuery(musicFolderQueries.toArray(new SpanQuery[musicFolderQueries.size()])); TopDocs topDocs = searcher.search(query, null, Integer.MAX_VALUE); List<ScoreDoc> scoreDocs = Lists.newArrayList(topDocs.scoreDocs); Random random = new Random(System.currentTimeMillis()); while (!scoreDocs.isEmpty() && result.size() < count) { int index = random.nextInt(scoreDocs.size()); Document doc = searcher.doc(scoreDocs.remove(index).doc); int id = Integer.valueOf(doc.get(FIELD_ID)); try { addIfNotNull(albumDao.getAlbum(id), result); } catch (Exception x) { LOG.warn("Failed to get album file " + id, x); } } } catch (Throwable x) { LOG.error("Failed to search for random albums.", x); } finally { FileUtil.closeQuietly(reader); } return result; }
/** Preferred usage: {@link MediaFileService#getmediaFile}. */ public MediaFile(int id, File file) { this.id = id; this.file = file; // Cache these values for performance. isFile = file.isFile(); isDirectory = file.isDirectory(); lastModified = file.lastModified(); isVideo = isFile && isVideoFile(file); if (isFile) { getMetaData(); LOG.debug("created file with id " + id + " from file " + file + ", metadata = " + metaData); } }
public synchronized void init() { // Clean up partial downloads. for (PodcastChannel channel : getAllChannels()) { for (PodcastEpisode episode : getEpisodes(channel.getId(), false)) { if (episode.getStatus() == PodcastStatus.DOWNLOADING) { deleteEpisode(episode.getId(), false); LOG.info( "Deleted Podcast episode '" + episode.getTitle() + "' since download was interrupted."); } } } schedule(); }
@SuppressWarnings({"unchecked"}) private void doRefreshChannel(PodcastChannel channel, boolean downloadEpisodes) { InputStream in = null; HttpClient client = new DefaultHttpClient(); try { channel.setStatus(PodcastStatus.DOWNLOADING); channel.setErrorMessage(null); podcastDao.updateChannel(channel); HttpConnectionParams.setConnectionTimeout(client.getParams(), 2 * 60 * 1000); // 2 minutes HttpConnectionParams.setSoTimeout(client.getParams(), 10 * 60 * 1000); // 10 minutes HttpGet method = new HttpGet(channel.getUrl()); HttpResponse response = client.execute(method); in = response.getEntity().getContent(); Document document = new SAXBuilder().build(in); Element channelElement = document.getRootElement().getChild("channel"); channel.setTitle(channelElement.getChildTextTrim("title")); channel.setDescription(channelElement.getChildTextTrim("description")); channel.setStatus(PodcastStatus.COMPLETED); channel.setErrorMessage(null); podcastDao.updateChannel(channel); refreshEpisodes(channel, channelElement.getChildren("item")); } catch (Exception x) { LOG.warn("Failed to get/parse RSS file for Podcast channel " + channel.getUrl(), x); channel.setStatus(PodcastStatus.ERROR); channel.setErrorMessage(x.toString()); podcastDao.updateChannel(channel); } finally { IOUtils.closeQuietly(in); client.getConnectionManager().shutdown(); } if (downloadEpisodes) { for (final PodcastEpisode episode : getEpisodes(channel.getId(), false)) { if (episode.getStatus() == PodcastStatus.NEW && episode.getUrl() != null) { downloadEpisode(episode); } } } }