Skip to content

Commit

Permalink
Allow defining paths to be excluded from the music library.
Browse files Browse the repository at this point in the history
The Settings view now has an option to define paths within the library base
path which shall be excluded from the music library. The exclude paths may
also be defined using wild cards ?, *, and **.

Any changes to the excluded paths currently take effect only upon rescan of
the library.

refs owncloud#762
  • Loading branch information
paulijar committed Oct 3, 2020
1 parent 3907008 commit e9e1c81
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 21 deletions.
3 changes: 2 additions & 1 deletion app/music.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ public function __construct(array $urlParams=[]) {
$c->query('UserId'),
$c->query('UserMusicFolder'),
$c->query('SecureRandom'),
$c->query('URLGenerator')
$c->query('URLGenerator'),
$c->query('Logger')
);
});

Expand Down
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
// settings
['name' => 'setting#getAll', 'url' => '/api/settings', 'verb' => 'GET'],
['name' => 'setting#userPath', 'url' => '/api/settings/user/path', 'verb' => 'POST'],
['name' => 'setting#userExcludedPaths', 'url' => '/api/settings/user/exclude_paths','verb' => 'POST'],
['name' => 'setting#addUserKey', 'url' => '/api/settings/userkey/add', 'verb' => 'POST'],
['name' => 'setting#generateUserKey', 'url' => '/api/settings/userkey/generate', 'verb' => 'POST'],
['name' => 'setting#removeUserKey', 'url' => '/api/settings/userkey/remove', 'verb' => 'POST'],
Expand Down
15 changes: 14 additions & 1 deletion controller/settingcontroller.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use \OCP\IURLGenerator;
use \OCP\Security\ISecureRandom;

use \OCA\Music\AppFramework\Core\Logger;
use \OCA\Music\Db\AmpacheUserMapper;
use \OCA\Music\Http\ErrorResponse;
use \OCA\Music\Utility\Scanner;
Expand All @@ -36,6 +37,7 @@ class SettingController extends Controller {
private $userMusicFolder;
private $secureRandom;
private $urlGenerator;
private $logger;

public function __construct($appName,
IRequest $request,
Expand All @@ -44,7 +46,8 @@ public function __construct($appName,
$userId,
UserMusicFolder $userMusicFolder,
ISecureRandom $secureRandom,
IURLGenerator $urlGenerator) {
IURLGenerator $urlGenerator,
Logger $logger) {
parent::__construct($appName, $request);

$this->ampacheUserMapper = $ampacheUserMapper;
Expand All @@ -53,6 +56,7 @@ public function __construct($appName,
$this->userMusicFolder = $userMusicFolder;
$this->secureRandom = $secureRandom;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
}

/**
Expand All @@ -70,12 +74,21 @@ public function userPath($value) {
return new JSONResponse(['success' => $success]);
}

/**
* @NoAdminRequired
*/
public function userExcludedPaths($value) {
$success = $this->userMusicFolder->setExcludedPaths($this->userId, $value);
return new JSONResponse(['success' => $success]);
}

/**
* @NoAdminRequired
*/
public function getAll() {
return [
'path' => $this->userMusicFolder->getPath($this->userId),
'excludedPaths' => $this->userMusicFolder->getExcludedPaths($this->userId),
'ampacheUrl' => $this->getAmpacheUrl(),
'subsonicUrl' => $this->getSubsonicUrl(),
'ampacheKeys' => $this->getAmpacheKeys(),
Expand Down
24 changes: 23 additions & 1 deletion css/style-settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
content: " ↗";
}

#music-user ul.info-list {
list-style: unset;
list-style-position: inside;
padding-left: 10px;
}

#music-user .label-container {
display: inline-block;
}
Expand All @@ -49,6 +55,10 @@
outline: 0;
}

#music-user #excluded-paths .key-action {
width: 0;
}

#music-user #reset-collection {
border: 0;
background-color: transparent;
Expand Down Expand Up @@ -100,11 +110,23 @@
text-align: right;
}

#music-user table td.key-action a {
#music-user table td.key-action a,
#music-user table .add-row td a {
padding: 10px 16px;
opacity: .5;
}

#music-user table .add-row,
#music-user table .add-row * {
border: none;
cursor: pointer;
}

#music-user table .excluded-path-row td {
padding-top: 3px;
padding-bottom: 3px;
}

#music-user .info, #music-user .warning {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
Expand Down
54 changes: 53 additions & 1 deletion js/app/controllers/settingsviewcontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ angular.module('Music').controller('SettingsViewController', [
$scope.ampacheClientsUrl = 'https://github.com/owncloud/music/wiki/Ampache';
$scope.subsonicClientsUrl = 'https://github.com/owncloud/music/wiki/Subsonic';

var savedExcludedPaths = [];

// $rootScope listeneres must be unsubscribed manually when the control is destroyed
var unsubFuncs = [];

Expand Down Expand Up @@ -72,6 +74,55 @@ angular.module('Music').controller('SettingsViewController', [
);
};

$scope.selectExcludedPath = function(index) {
OC.dialogs.filepicker(
gettextCatalog.getString('Path to exclude from your music collection'),
function (path) {
if (path.substr(-1) !== '/') {
path = path + '/';
}
$scope.settings.excludedPaths[index] = path;
$scope.commitExcludedPaths();
},
false,
'httpd/unix-directory',
true
);
};

$scope.removeExcludedPath = function(index) {
$scope.settings.excludedPaths.splice(index, 1);
$scope.commitExcludedPaths();
};

$scope.addExcludedPath = function() {
$scope.settings.excludedPaths.push('');
// no commit here, as the empty path is meaningless
};

$scope.commitExcludedPaths = function() {
// Get the entered paths, trimming excess white space and filtering out any empty paths
var paths = $scope.settings.excludedPaths;
paths = _.map(paths, function(path) { return path.trim(); });
paths = _.filter(paths, function(path) { return path !== ''; });

// Send the paths to the back-end if there are any changes
if (!_.isEqual(paths, savedExcludedPaths)) {
$scope.savingExcludedPaths = true;
Restangular.all('settings/user/exclude_paths').post({value: paths}).then(
function(data) {
// success
$scope.savingExcludedPaths = false;
savedExcludedPaths = paths;
},
function(response) {
// error handling
$scope.savingExcludedPaths = false;
}
);
}
};

$scope.resetCollection = function() {
OC.dialogs.confirm(
gettextCatalog.getString('Are you sure to reset the music collection? This removes all scanned tracks and user-created playlists!'),
Expand All @@ -88,7 +139,7 @@ angular.module('Music').controller('SettingsViewController', [
var parent = $scope.$parent;
var executeReset = function() {
Restangular.all('resetscanned').post().then(
function (data) {
function(data) {
if (data.success) {
parent.resetScanned();
parent.update();
Expand Down Expand Up @@ -166,6 +217,7 @@ angular.module('Music').controller('SettingsViewController', [
Restangular.one('settings').get().then(function (value) {
$scope.settings = value;
$rootScope.loading = false;
savedExcludedPaths = _.clone(value.excludedPaths);
});
});

Expand Down
8 changes: 5 additions & 3 deletions js/app/directives/onEnter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
* later. See the COPYING file.
*
* @author Volkan Gezer <volkangezer@gmail.com>
* @author Pauli Järvinen <pauli.jarvinen@gmail.com>
* @copyright 2014 Volkan Gezer
* @copyright 2020 Pauli Järvinen
*
*/

angular.module('Music').directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
if (event.which === 13) {
scope.$apply(function () {
scope.$eval(attrs.ngEnter, {$event: event});
});
event.preventDefault();
}
Expand Down
36 changes: 35 additions & 1 deletion templates/partials/settingsview.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,40 @@
<p><em translate>This setting specifies the folder which will be scanned for music.</em></p>
<p><em translate>Note: When the path is changed, any previously scanned files outside the new path are removed from the collection and any playlists.</em></p>
</div>
<div>
<div class="label-container">
<label for="excluded-paths" translate>Paths to exclude from your music collection</label>:
</div>
<em>
<p translate>Specify folders within your music collection path which shall be excluded from the scanning.
<strong class="clickable" ng-click="showExcludeHint=true" ng-hide="showExcludeHint" translate>Show more…</strong>
</p>
<div ng-show="showExcludeHint">
<p translate>You can use the wild cards '?', '*', and '**':</p>
<ul class="info-list">
<li translate><strong>?</strong> matches any one character within a path segment</li>
<li translate><strong>*</strong> matches zero or more arbitrary characters within a path segment</li>
<li translate><strong>**</strong> matches zero or more arbitrary characters including path segment separators '/'</li>
</ul>
<p translate>Paths with a leading '/' character are resolved relative to the user home directory and others relative to the music library base path.</p>
<p translate>Changes to the excluded paths will only take effect upon rescan.</p>
</div>
</em>
<table id="excluded-paths" class="grid" ng-show="settings.ampacheKeys.length">
<tr class="excluded-path-row" ng-repeat="path in settings.excludedPaths track by $index">
<td><input type="text" ng-model="settings.excludedPaths[$index]" ng-enter="$event.target.blur()" ng-blur="commitExcludedPaths()"/></td>
<td class="key-action"><a class="icon icon-folder" ng-click="selectExcludedPath($index)" title="{{ 'Select folder' | translate }}"></a></td>
<td class="key-action"><a class="icon icon-delete" ng-click="removeExcludedPath($index)" title="{{ 'Remove' | translate }}"></a></td>
</tr>
<tr class="add-row" ng-click="addExcludedPath()">
<td><a class="icon" ng-class="savingExcludedPaths ? 'icon-loading-small' : 'icon-add'"></a></td>
<td class="key-action"></td>
<td class="key-action"></td>
</tr>
</table>
</div>

<h2 translate>Reset</h2>
<div>
<div class="label-container">
<label for="reset-collection" translate>Reset music collection</label>
Expand All @@ -19,7 +53,7 @@
<input type="button" ng-class="{ 'invisible': resetOngoing }" class="icon-delete"
id="reset-collection" ng-click="resetCollection()"/>
<p><em translate>This action resets all the scanned tracks and all the user-created playlists. After this, the collection can be scanned again from scratch.</em></p>
<p><em translate>There should usually be no need to do this. In case you find it necessary, you have probably found a bug which should be reported to the <a href="{{issueTrackerUrl}}" target="_blank">issues</a>.</em></p>
<p><em translate>This may be desirable after changing the excluded paths, or if the database would somehow get corrupted. If the latter happens, please report a bug to the <a href="{{issueTrackerUrl}}" target="_blank">issue tracker</a>.</em></p>
</div>

<h2 translate>Ampache and Subsonic</h2>
Expand Down
24 changes: 12 additions & 12 deletions utility/scanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function update($file, $userId, $userHome, $filePath = null) {
}

// skip files that aren't inside the user specified path
if (!$this->pathIsUnderMusicFolder($filePath, $userId)) {
if (!$this->userMusicFolder->pathBelongsToMusicLibrary($filePath, $userId)) {
$this->logger->log("skipped - file is outside of specified music folder", 'debug');
return;
}
Expand All @@ -120,12 +120,6 @@ private static function isPlaylistMime($mime) {
return $mime == 'audio/mpegurl' || $mime == 'audio/x-scpls';
}

private function pathIsUnderMusicFolder($filePath, $userId) {
$musicFolder = $this->userMusicFolder->getFolder($userId);
$musicPath = $musicFolder->getPath();
return Util::startsWith($filePath, $musicPath);
}

private function updateImage($file, $userId) {
$coverFileId = $file->getId();
$parentFolderId = $file->getParent()->getId();
Expand Down Expand Up @@ -424,10 +418,11 @@ private function getMusicFiles($userId, $path = null) {
return [];
}

// Search files with mime 'audio/*' but filter out the playlist files
// Search files with mime 'audio/*' but filter out the playlist files and files under excluded folders
$files = $folder->searchByMime('audio');
return \array_filter($files, function ($f) {
return !self::isPlaylistMime($f->getMimeType());
return \array_filter($files, function ($f) use ($userId) {
return !self::isPlaylistMime($f->getMimeType())
&& $this->userMusicFolder->pathBelongsToMusicLibrary($f->getPath(), $userId);
});
}

Expand All @@ -444,7 +439,12 @@ private function getImageFiles($userId) {
return [];
}

return $folder->searchByMime('image');
$images = $folder->searchByMime('image');

// filter out any images in the excluded folders
return \array_filter($images, function($image) use ($userId) {
return $this->userMusicFolder->pathBelongsToMusicLibrary($image->getPath(), $userId);
});
}

private function getScannedFileIds($userId) {
Expand Down Expand Up @@ -598,7 +598,7 @@ private function getUnindexedFileInfo($fileId, $userId, $userFolder) {
'title' => $metadata['title'],
'artist' => $metadata['artist'],
'cover' => $cover,
'in_library' => $this->pathIsUnderMusicFolder($file->getPath(), $userId)
'in_library' => $this->userMusicFolder->pathBelongsToMusicLibrary($file->getPath(), $userId)
];
}
return null;
Expand Down
Loading

0 comments on commit e9e1c81

Please sign in to comment.