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.repository;
22 
23 import java.io.File;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 
31 import net.sourceforge.atunes.kernel.AbstractHandler;
32 import net.sourceforge.atunes.model.IAlbum;
33 import net.sourceforge.atunes.model.IArtist;
34 import net.sourceforge.atunes.model.IAudioFilesRemovedListener;
35 import net.sourceforge.atunes.model.IAudioObject;
36 import net.sourceforge.atunes.model.IBackgroundWorker;
37 import net.sourceforge.atunes.model.IBackgroundWorkerFactory;
38 import net.sourceforge.atunes.model.IDialogFactory;
39 import net.sourceforge.atunes.model.IFavoritesHandler;
40 import net.sourceforge.atunes.model.IFileManager;
41 import net.sourceforge.atunes.model.IFolder;
42 import net.sourceforge.atunes.model.IGenre;
43 import net.sourceforge.atunes.model.IIndeterminateProgressDialog;
44 import net.sourceforge.atunes.model.ILocalAudioObject;
45 import net.sourceforge.atunes.model.IMessageDialog;
46 import net.sourceforge.atunes.model.INavigationHandler;
47 import net.sourceforge.atunes.model.IRepository;
48 import net.sourceforge.atunes.model.IRepositoryHandler;
49 import net.sourceforge.atunes.model.IRepositoryTransaction;
50 import net.sourceforge.atunes.model.IStateRepository;
51 import net.sourceforge.atunes.model.IStateService;
52 import net.sourceforge.atunes.model.IStatisticsHandler;
53 import net.sourceforge.atunes.model.ITaskService;
54 import net.sourceforge.atunes.model.ITrackInfo;
55 import net.sourceforge.atunes.model.IUnknownObjectChecker;
56 import net.sourceforge.atunes.model.IYear;
57 import net.sourceforge.atunes.model.ViewMode;
58 import net.sourceforge.atunes.utils.CollectionUtils;
59 import net.sourceforge.atunes.utils.I18nUtils;
60 import net.sourceforge.atunes.utils.Logger;
61 import net.sourceforge.atunes.utils.StringUtils;
62 
63 /**
64  * The repository handler.
65  */
66 public final class RepositoryHandler extends AbstractHandler implements
67 		IRepositoryHandler {
68 
69 	private boolean caseSensitiveTrees;
70 
71 	private boolean storeRatingInFile;
72 
73 	private IStatisticsHandler statisticsHandler;
74 
75 	private INavigationHandler navigationHandler;
76 
77 	private IStateService stateService;
78 
79 	private IRepository repository;
80 
81 	private RepositoryAutoRefresher repositoryRefresher;
82 
83 	/** Listeners notified when an audio file is removed */
84 	private final List<IAudioFilesRemovedListener> audioFilesRemovedListeners = new ArrayList<IAudioFilesRemovedListener>();
85 
86 	private IFavoritesHandler favoritesHandler;
87 
88 	private IRepositoryTransaction transaction;
89 
90 	private IStateRepository stateRepository;
91 
92 	private IBackgroundWorkerFactory backgroundWorkerFactory;
93 
94 	private IDialogFactory dialogFactory;
95 
96 	private List<File> foldersSelectedFromPreferences;
97 
98 	private IUnknownObjectChecker unknownObjectChecker;
99 
100 	private IFileManager fileManager;
101 
102 	private RepositoryReader currentRepositoryReader;
103 
104 	private ITaskService taskService;
105 
106 	private boolean repositoryNotSelected;
107 
108 	/**
109 	 * @param taskService
110 	 */
setTaskService(final ITaskService taskService)111 	public void setTaskService(final ITaskService taskService) {
112 		this.taskService = taskService;
113 	}
114 
115 	/**
116 	 * @param fileManager
117 	 */
setFileManager(final IFileManager fileManager)118 	public void setFileManager(final IFileManager fileManager) {
119 		this.fileManager = fileManager;
120 	}
121 
122 	/**
123 	 * @param unknownObjectChecker
124 	 */
setUnknownObjectChecker( final IUnknownObjectChecker unknownObjectChecker)125 	public void setUnknownObjectChecker(
126 			final IUnknownObjectChecker unknownObjectChecker) {
127 		this.unknownObjectChecker = unknownObjectChecker;
128 	}
129 
130 	/**
131 	 * @param dialogFactory
132 	 */
setDialogFactory(final IDialogFactory dialogFactory)133 	public void setDialogFactory(final IDialogFactory dialogFactory) {
134 		this.dialogFactory = dialogFactory;
135 	}
136 
137 	/**
138 	 * @param backgroundWorkerFactory
139 	 */
setBackgroundWorkerFactory( final IBackgroundWorkerFactory backgroundWorkerFactory)140 	public void setBackgroundWorkerFactory(
141 			final IBackgroundWorkerFactory backgroundWorkerFactory) {
142 		this.backgroundWorkerFactory = backgroundWorkerFactory;
143 	}
144 
145 	/**
146 	 * @param stateRepository
147 	 */
setStateRepository(final IStateRepository stateRepository)148 	public void setStateRepository(final IStateRepository stateRepository) {
149 		this.stateRepository = stateRepository;
150 	}
151 
152 	/**
153 	 * @param repository
154 	 */
setRepository(final IRepository repository)155 	void setRepository(final IRepository repository) {
156 		this.repository = repository;
157 		setRepositoryNotSelected(repository == null
158 				|| (repository instanceof VoidRepository));
159 	}
160 
161 	/**
162 	 * @param favoritesHandler
163 	 */
setFavoritesHandler(final IFavoritesHandler favoritesHandler)164 	public void setFavoritesHandler(final IFavoritesHandler favoritesHandler) {
165 		this.favoritesHandler = favoritesHandler;
166 	}
167 
168 	/**
169 	 * @param stateService
170 	 */
setStateService(final IStateService stateService)171 	public void setStateService(final IStateService stateService) {
172 		this.stateService = stateService;
173 	}
174 
175 	/**
176 	 * @param statisticsHandler
177 	 */
setStatisticsHandler(final IStatisticsHandler statisticsHandler)178 	public void setStatisticsHandler(final IStatisticsHandler statisticsHandler) {
179 		this.statisticsHandler = statisticsHandler;
180 	}
181 
182 	/**
183 	 * @param navigationHandler
184 	 */
setNavigationHandler(final INavigationHandler navigationHandler)185 	public void setNavigationHandler(final INavigationHandler navigationHandler) {
186 		this.navigationHandler = navigationHandler;
187 	}
188 
189 	/**
190 	 * @param repositoryRefresher
191 	 */
setRepositoryRefresher( final RepositoryAutoRefresher repositoryRefresher)192 	public void setRepositoryRefresher(
193 			final RepositoryAutoRefresher repositoryRefresher) {
194 		this.repositoryRefresher = repositoryRefresher;
195 	}
196 
197 	@Override
applicationStateChanged()198 	public void applicationStateChanged() {
199 		// User changed repository folders
200 		if (this.foldersSelectedFromPreferences != null) {
201 			this.currentRepositoryReader = getBean(RepositoryReader.class);
202 			this.currentRepositoryReader
203 					.newRepositoryWithFolders(this.foldersSelectedFromPreferences);
204 			this.foldersSelectedFromPreferences = null;
205 		} else if (checkPropertiesToTrackForReload()) {
206 			reloadRepository();
207 		}
208 		// Reschedule repository refresher
209 		this.repositoryRefresher.start();
210 	}
211 
212 	@Override
initHandler()213 	protected void initHandler() {
214 		// Initially use void repository until one is loaded or selected
215 		this.repository = getBean(VoidRepository.class);
216 
217 		setPropertiesToTrackForReload();
218 
219 		// Add itself as listener
220 		addAudioFilesRemovedListener(this);
221 	}
222 
setPropertiesToTrackForReload()223 	private void setPropertiesToTrackForReload() {
224 		// A change in these properties needs a repository reload
225 		this.caseSensitiveTrees = this.stateRepository
226 				.isKeyAlwaysCaseSensitiveInRepositoryStructure();
227 		this.storeRatingInFile = this.stateRepository.isStoreRatingInFile();
228 	}
229 
checkPropertiesToTrackForReload()230 	private boolean checkPropertiesToTrackForReload() {
231 		boolean reload = false;
232 		// Check properties and return true if needs to reload repository
233 		// Update properties to keep track or further changes
234 		if (this.caseSensitiveTrees != this.stateRepository
235 				.isKeyAlwaysCaseSensitiveInRepositoryStructure()) {
236 			reload = true;
237 			this.caseSensitiveTrees = this.stateRepository
238 					.isKeyAlwaysCaseSensitiveInRepositoryStructure();
239 		}
240 		if (this.storeRatingInFile != this.stateRepository
241 				.isStoreRatingInFile()) {
242 			reload = true;
243 			this.storeRatingInFile = this.stateRepository.isStoreRatingInFile();
244 		}
245 
246 		return reload;
247 	}
248 
249 	@Override
addAudioObjectsAndRefresh(final List<ILocalAudioObject> result)250 	public void addAudioObjectsAndRefresh(final List<ILocalAudioObject> result) {
251 		getBean(AddFilesToRepositoryTask.class)
252 				.execute(this.repository, result);
253 	}
254 
255 	/**
256 	 * Finish.
257 	 */
258 	@Override
applicationFinish()259 	public void applicationFinish() {
260 		this.repositoryRefresher.stop();
261 		if (!isRepositoryVoid()) {
262 			// Only store repository if it's dirty
263 			if (transactionPending()) {
264 				this.stateService.persistRepositoryCache(this.repository);
265 			} else {
266 				Logger.info("Repository is clean");
267 			}
268 
269 			// Execute command after last access to repository
270 			new LoadRepositoryCommandExecutor().execute(this.stateRepository
271 					.getCommandAfterAccessRepository());
272 		}
273 	}
274 
275 	@Override
getFolders()276 	public List<File> getFolders() {
277 		return this.repository.getRepositoryFolders();
278 	}
279 
280 	@Override
getAlbums()281 	public List<IAlbum> getAlbums() {
282 		List<IAlbum> result = new ArrayList<IAlbum>();
283 		Collection<IArtist> artists = this.repository.getArtists();
284 		for (IArtist a : artists) {
285 			result.addAll(a.getAlbums().values());
286 		}
287 		Collections.sort(result);
288 		return result;
289 	}
290 
291 	@Override
getArtists()292 	public List<IArtist> getArtists() {
293 		List<IArtist> result = new ArrayList<IArtist>();
294 		result.addAll(this.repository.getArtists());
295 		Collections.sort(result);
296 		return result;
297 	}
298 
299 	@Override
getArtist(final String name)300 	public IArtist getArtist(final String name) {
301 		return this.repository.getArtist(name);
302 	}
303 
304 	@Override
removeArtist(final IArtist artist)305 	public void removeArtist(final IArtist artist) {
306 		this.repository.removeArtist(artist);
307 	}
308 
309 	@Override
getGenre(final String genre)310 	public IGenre getGenre(final String genre) {
311 		return this.repository.getGenre(genre);
312 	}
313 
314 	@Override
removeGenre(final IGenre genre)315 	public void removeGenre(final IGenre genre) {
316 		this.repository.removeGenre(genre);
317 	}
318 
319 	@Override
getFileIfLoaded(final String fileName)320 	public ILocalAudioObject getFileIfLoaded(final String fileName) {
321 		return this.repository.getFile(fileName);
322 	}
323 
324 	@Override
getFoldersCount()325 	public int getFoldersCount() {
326 		return this.repository.getRepositoryFolders().size();
327 	}
328 
329 	@Override
getRepositoryFolderContainingFile(final ILocalAudioObject file)330 	public File getRepositoryFolderContainingFile(final ILocalAudioObject file) {
331 		for (File folder : this.repository.getRepositoryFolders()) {
332 			if (file.getUrl().startsWith(
333 					net.sourceforge.atunes.utils.FileUtils.getPath(folder))) {
334 				return folder;
335 			}
336 		}
337 		return null;
338 	}
339 
340 	@Override
getRepositoryPath()341 	public String getRepositoryPath() {
342 		// TODO: Remove this method as now more than one folder can be added to
343 		// repository
344 		return this.repository.getRepositoryFolders().size() > 0 ? net.sourceforge.atunes.utils.FileUtils
345 				.getPath(this.repository.getRepositoryFolders().get(0)) : "";
346 	}
347 
348 	@Override
getRepositoryTotalSize()349 	public long getRepositoryTotalSize() {
350 		return this.repository.getTotalSizeInBytes();
351 	}
352 
353 	@Override
getNumberOfFiles()354 	public int getNumberOfFiles() {
355 		return this.repository.countFiles();
356 	}
357 
358 	@Override
getAudioFilesList()359 	public Collection<ILocalAudioObject> getAudioFilesList() {
360 		return this.repository.getFiles();
361 	}
362 
363 	@Override
getAudioFilesForAlbums( final List<IAlbum> albums)364 	public List<ILocalAudioObject> getAudioFilesForAlbums(
365 			final List<IAlbum> albums) {
366 		List<ILocalAudioObject> result = new ArrayList<ILocalAudioObject>();
367 		for (IAlbum album : albums) {
368 			result.addAll(album.getAudioObjects());
369 		}
370 		return result;
371 	}
372 
373 	@Override
getAudioFilesForArtists( final List<IArtist> artists)374 	public List<ILocalAudioObject> getAudioFilesForArtists(
375 			final List<IArtist> artists) {
376 		List<ILocalAudioObject> result = new ArrayList<ILocalAudioObject>();
377 		for (IArtist artist : artists) {
378 			result.addAll(artist.getAudioObjects());
379 		}
380 		return result;
381 	}
382 
383 	@Override
isRepository(final File folder)384 	public boolean isRepository(final File folder) {
385 		String path = net.sourceforge.atunes.utils.FileUtils.getPath(folder);
386 		for (File folders : this.repository.getRepositoryFolders()) {
387 			if (path.startsWith(net.sourceforge.atunes.utils.FileUtils
388 					.getPath(folders))) {
389 				return true;
390 			}
391 		}
392 		return false;
393 	}
394 
395 	@Override
notifyCancel()396 	public void notifyCancel() {
397 		if (this.currentRepositoryReader != null) {
398 			this.currentRepositoryReader.notifyCancel();
399 		}
400 		this.currentRepositoryReader = null;
401 	}
402 
403 	@Override
refreshFiles(final List<ILocalAudioObject> files)404 	public void refreshFiles(final List<ILocalAudioObject> files) {
405 		startTransaction();
406 		LocalAudioObjectRefresher refresher = getBean(LocalAudioObjectRefresher.class);
407 		for (ILocalAudioObject file : files) {
408 			refresher.refreshFile(this.repository, file);
409 		}
410 		endTransaction();
411 	}
412 
413 	@Override
refreshFolders(final List<IFolder> folders)414 	public void refreshFolders(final List<IFolder> folders) {
415 		getBean(RefreshFoldersTask.class).execute(this.repository, folders);
416 	}
417 
418 	@Override
refreshRepository()419 	public void refreshRepository() {
420 		if (!isRepositoryVoid()) {
421 			this.currentRepositoryReader = getBean(RepositoryReader.class);
422 			this.currentRepositoryReader.refresh(this.repository);
423 		}
424 	}
425 
426 	@Override
reloadRepository()427 	public void reloadRepository() {
428 		if (!isRepositoryVoid()) {
429 			this.dialogFactory.newDialog(IMessageDialog.class).showMessage(
430 					I18nUtils.getString("RELOAD_REPOSITORY_MESSAGE"));
431 			this.currentRepositoryReader = getBean(RepositoryReader.class);
432 			this.currentRepositoryReader
433 					.newRepositoryWithFoldersReloaded(this.repository
434 							.getRepositoryFolders());
435 		}
436 	}
437 
438 	@Override
removeFolders(final List<IFolder> foldersToRemove)439 	public void removeFolders(final List<IFolder> foldersToRemove) {
440 		startTransaction();
441 		removeFoldersInsideTransaction(foldersToRemove);
442 		endTransaction();
443 	}
444 
removeFoldersInsideTransaction( final List<IFolder> foldersToRemove)445 	private void removeFoldersInsideTransaction(
446 			final List<IFolder> foldersToRemove) {
447 		if (foldersToRemove == null || foldersToRemove.isEmpty()) {
448 			return;
449 		}
450 
451 		for (IFolder folder : foldersToRemove) {
452 
453 			// Remove content
454 			remove(folder.getAudioObjects());
455 
456 			// Remove from model
457 			if (folder.getParentFolder() != null) {
458 				folder.getParentFolder().removeFolder(folder);
459 			}
460 		}
461 	}
462 
463 	@Override
remove(final List<ILocalAudioObject> filesToRemove)464 	public void remove(final List<ILocalAudioObject> filesToRemove) {
465 		if (filesToRemove == null || filesToRemove.isEmpty()) {
466 			return;
467 		}
468 
469 		startTransaction();
470 		removeInsideTransaction(filesToRemove);
471 		endTransaction();
472 	}
473 
removeInsideTransaction( final List<ILocalAudioObject> filesToRemove)474 	private void removeInsideTransaction(
475 			final List<ILocalAudioObject> filesToRemove) {
476 		RepositoryRemover remover = getBean(RepositoryRemover.class);
477 		for (ILocalAudioObject fileToRemove : filesToRemove) {
478 			remover.deleteFile(fileToRemove);
479 		}
480 
481 		// Notify listeners
482 		for (IAudioFilesRemovedListener listener : this.audioFilesRemovedListeners) {
483 			listener.audioFilesRemoved(filesToRemove);
484 		}
485 	}
486 
487 	@Override
rename(final ILocalAudioObject audioFile, final String name)488 	public void rename(final ILocalAudioObject audioFile, final String name) {
489 		String oldName = this.fileManager.getPath(audioFile);
490 		if (this.fileManager.rename(audioFile, name)) {
491 			startTransaction();
492 			this.repository.removeFile(oldName);
493 			this.repository.putFile(audioFile);
494 			endTransaction();
495 			this.navigationHandler.repositoryReloaded();
496 			this.statisticsHandler.updateFileName(audioFile, oldName,
497 					this.fileManager.getPath(audioFile));
498 		}
499 	}
500 
501 	/**
502 	 * Returns if Repository is void (not yet loaded or selected)
503 	 *
504 	 * @return
505 	 */
isRepositoryVoid()506 	private boolean isRepositoryVoid() {
507 		return this.repository instanceof VoidRepository;
508 	}
509 
510 	@Override
addFolderToRepository()511 	public boolean addFolderToRepository() {
512 		this.currentRepositoryReader = getBean(RepositoryReader.class);
513 		return this.currentRepositoryReader
514 				.addFolderToRepository(this.repository);
515 	}
516 
517 	@Override
addAudioFilesRemovedListener( final IAudioFilesRemovedListener listener)518 	public void addAudioFilesRemovedListener(
519 			final IAudioFilesRemovedListener listener) {
520 		this.audioFilesRemovedListeners.add(listener);
521 	}
522 
523 	@Override
audioFilesRemoved(final List<ILocalAudioObject> audioFiles)524 	public void audioFilesRemoved(final List<ILocalAudioObject> audioFiles) {
525 		// Update status bar
526 		getBean(ShowRepositoryDataHelper.class).showRepositoryAudioFileNumber(
527 				getAudioFilesList().size(), getRepositoryTotalSize(),
528 				this.repository.getTotalDurationInSeconds());
529 	}
530 
531 	@Override
doInBackground()532 	public void doInBackground() {
533 		if (this.currentRepositoryReader != null) {
534 			this.currentRepositoryReader.doInBackground();
535 		}
536 	}
537 
538 	/**
539 	 * Returns <code>true</code>if there is a loader reading or refreshing
540 	 * repository
541 	 *
542 	 * @return
543 	 */
isLoaderWorking()544 	protected boolean isLoaderWorking() {
545 		return this.currentRepositoryReader != null
546 				&& this.currentRepositoryReader.isWorking();
547 	}
548 
549 	@Override
repositoryChanged(final IRepository repository)550 	public void repositoryChanged(final IRepository repository) {
551 		getBean(PersistRepositoryTask.class).persist(repository);
552 		this.favoritesHandler.updateFavoritesAfterRepositoryChange(repository);
553 	}
554 
startTransaction()555 	protected final void startTransaction() {
556 		this.transaction = new RepositoryTransaction(this.repository, this);
557 	}
558 
endTransaction()559 	protected final void endTransaction() {
560 		if (this.transaction != null) {
561 			this.transaction.finishTransaction();
562 		}
563 	}
564 
transactionPending()565 	private boolean transactionPending() {
566 		return this.transaction != null && this.transaction.isPending();
567 	}
568 
569 	@Override
getDataForView(final ViewMode viewMode)570 	public Map<String, ?> getDataForView(final ViewMode viewMode) {
571 		return viewMode.getDataForView(this.repository);
572 	}
573 
574 	@Override
getFile(final String fileName)575 	public ILocalAudioObject getFile(final String fileName) {
576 		return this.repository.getFile(fileName);
577 	}
578 
579 	@Override
getYear(final String year)580 	public IYear getYear(final String year) {
581 		return this.repository.getYear(year);
582 	}
583 
584 	@Override
removeYear(final IYear year)585 	public void removeYear(final IYear year) {
586 		this.repository.removeYear(year, this.unknownObjectChecker);
587 	}
588 
589 	@Override
removeFile(final ILocalAudioObject file)590 	public void removeFile(final ILocalAudioObject file) {
591 		this.repository.removeFile(file);
592 		this.repository.removeSizeInBytes(this.fileManager.getFileSize(file));
593 		this.repository.removeDurationInSeconds(file.getDuration());
594 	}
595 
596 	@Override
getFolder(final String path)597 	public IFolder getFolder(final String path) {
598 		return this.repository.getFolder(path);
599 	}
600 
601 	@Override
getAudioObjectsByTitle( final String artistName, final List<String> titlesList)602 	public List<ILocalAudioObject> getAudioObjectsByTitle(
603 			final String artistName, final List<String> titlesList) {
604 		if (StringUtils.isEmpty(artistName)) {
605 			throw new IllegalArgumentException("Invalid artist name");
606 		}
607 		List<ILocalAudioObject> result = new ArrayList<ILocalAudioObject>();
608 		IArtist artist = getArtist(artistName);
609 		if (artist != null) {
610 			Map<String, ILocalAudioObject> normalizedTitles = getNormalizedAudioObjectsTitles(artist);
611 			for (String title : titlesList) {
612 				String normalizedTitle = title.toLowerCase();
613 				if (normalizedTitles.containsKey(normalizedTitle)) {
614 					result.add(normalizedTitles.get(normalizedTitle));
615 				}
616 			}
617 		}
618 		return result;
619 	}
620 
621 	@Override
checkAvailability(final String artist, final List<ITrackInfo> tracks)622 	public void checkAvailability(final String artist,
623 			final List<ITrackInfo> tracks) {
624 		if (StringUtils.isEmpty(artist)) {
625 			throw new IllegalArgumentException("Invalid artist name");
626 		}
627 		if (!CollectionUtils.isEmpty(tracks)) {
628 			IArtist a = getArtist(artist);
629 			if (a != null) {
630 				Map<String, ILocalAudioObject> normalizedTitles = getNormalizedAudioObjectsTitles(a);
631 				for (ITrackInfo track : tracks) {
632 					track.setAvailable(normalizedTitles.containsKey(track
633 							.getTitle().toLowerCase()));
634 				}
635 			}
636 		}
637 	}
638 
639 	/**
640 	 *
641 	 * @param artist
642 	 * @return
643 	 */
getNormalizedAudioObjectsTitles( final IArtist artist)644 	private Map<String, ILocalAudioObject> getNormalizedAudioObjectsTitles(
645 			final IArtist artist) {
646 		List<ILocalAudioObject> audioObjects = artist.getAudioObjects();
647 		Map<String, ILocalAudioObject> titles = new HashMap<String, ILocalAudioObject>();
648 		for (ILocalAudioObject lao : audioObjects) {
649 			if (lao.getTitle() != null) {
650 				titles.put(lao.getTitle().toLowerCase(), lao); // Do lower case
651 																// for a better
652 																// match
653 			}
654 		}
655 		return titles;
656 	}
657 
658 	@Override
importFolders(final List<File> folders, final String path)659 	public void importFolders(final List<File> folders, final String path) {
660 		final IIndeterminateProgressDialog indeterminateDialog = this.dialogFactory
661 				.newDialog(IIndeterminateProgressDialog.class);
662 		indeterminateDialog.setTitle(StringUtils.getString(
663 				I18nUtils.getString("READING_FILES_TO_IMPORT"), "..."));
664 
665 		IBackgroundWorker<List<ILocalAudioObject>, Void> worker = this.backgroundWorkerFactory
666 				.getWorker();
667 		worker.setActionsBeforeBackgroundStarts(new ShowIndeterminateDialogRunnable(
668 				indeterminateDialog));
669 		ImportFoldersToRepositoryCallable callable = getBean(ImportFoldersToRepositoryCallable.class);
670 		callable.setFolders(folders);
671 		worker.setBackgroundActions(callable);
672 		ImportFoldersToRepositoryActionsWithBackgroundResult actionsWhenDone = getBean(ImportFoldersToRepositoryActionsWithBackgroundResult.class);
673 		actionsWhenDone.setFolders(folders);
674 		actionsWhenDone.setPath(path);
675 		actionsWhenDone.setIndeterminateDialog(indeterminateDialog);
676 		worker.setActionsWhenDone(actionsWhenDone);
677 		worker.execute(this.taskService);
678 	}
679 
680 	@Override
setRepositoryFolders(final List<File> folders)681 	public void setRepositoryFolders(final List<File> folders) {
682 		this.foldersSelectedFromPreferences = folders;
683 	}
684 
685 	@Override
folderMoved(final IFolder sourceFolder, final File destination)686 	public void folderMoved(final IFolder sourceFolder, final File destination) {
687 		startTransaction();
688 		removeFoldersInsideTransaction(Collections.singletonList(sourceFolder));
689 		getBean(RepositoryAddService.class)
690 				.addFoldersToRepositoryInsideTransaction(this.repository,
691 						Collections.singletonList(destination));
692 		getBean(ShowRepositoryDataHelper.class).showRepositoryAudioFileNumber(
693 				this.repository.getFiles().size(),
694 				this.repository.getTotalSizeInBytes(),
695 				this.repository.getTotalDurationInSeconds());
696 		endTransaction();
697 	}
698 
699 	@Override
getAudioObjectIfLoaded(final IAudioObject ao)700 	public IAudioObject getAudioObjectIfLoaded(final IAudioObject ao) {
701 		if (ao instanceof ILocalAudioObject) {
702 			IAudioObject cachedAO = getFileIfLoaded(ao.getUrl());
703 			if (cachedAO != null) {
704 				return cachedAO;
705 			}
706 		}
707 		return ao;
708 	}
709 
710 	/**
711 	 * Called when repository read
712 	 */
notifyRepositoryRead()713 	void notifyRepositoryRead() {
714 		this.currentRepositoryReader = null;
715 	}
716 
717 	@Override
existsArtist(final IArtist artist)718 	public boolean existsArtist(final IArtist artist) {
719 		if (artist != null) {
720 			return this.repository.getArtist(artist.getName()) != null;
721 		}
722 		return false;
723 	}
724 
725 	@Override
existsArtist(final String artist)726 	public boolean existsArtist(final String artist) {
727 		return this.repository.getArtist(artist) != null;
728 	}
729 
730 	@Override
existsAlbum(final IAlbum album)731 	public boolean existsAlbum(final IAlbum album) {
732 		if (album != null) {
733 			IArtist artist = this.repository.getArtist(album.getArtist()
734 					.getName());
735 			if (artist != null) {
736 				if (artist.getAlbum(album.getName()) != null) {
737 					return true;
738 				}
739 			}
740 		}
741 		return false;
742 	}
743 
744 	@Override
existsAlbum(final String artistName, final String album)745 	public boolean existsAlbum(final String artistName, final String album) {
746 		IArtist artist = this.repository.getArtist(artistName);
747 		if (artist != null) {
748 			if (artist.getAlbum(album) != null) {
749 				return true;
750 			}
751 		}
752 		return false;
753 	}
754 
755 	@Override
existsFile(final ILocalAudioObject ao)756 	public boolean existsFile(final ILocalAudioObject ao) {
757 		return getFileIfLoaded(ao.getUrl()) != null;
758 	}
759 
760 	@Override
isRepositoryNotSelected()761 	public boolean isRepositoryNotSelected() {
762 		return this.repositoryNotSelected;
763 	}
764 
setRepositoryNotSelected(final boolean notSelected)765 	protected void setRepositoryNotSelected(final boolean notSelected) {
766 		this.repositoryNotSelected = notSelected;
767 		// Force navigation view refresh to show information about repository
768 		// not selected
769 		this.navigationHandler.refreshCurrentView();
770 	}
771 }
772