Passed
Push — master ( 732353...fd8722 )
by Pauli
02:18
created

FileHooks   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 37
c 0
b 0
f 0
dl 0
loc 105
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A updated() 0 19 3
A deleted() 0 9 2
A __construct() 0 2 1
A userHasMusicLib() 0 3 1
A postRenamed() 0 6 1
A register() 0 5 1
A handleUpdated() 0 18 6
A preRenamed() 0 6 2
1
<?php declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Morris Jobke <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2014
12
 * @copyright Pauli Järvinen 2017 - 2021
13
 */
14
15
namespace OCA\Music\Hooks;
16
17
use OCP\AppFramework\IAppContainer;
18
use OCP\Files\FileInfo;
19
use OCP\Files\Node;
20
21
use OCA\Music\App\Music;
22
23
class FileHooks {
24
	private $filesystemRoot;
25
26
	public function __construct($filesystemRoot) {
27
		$this->filesystemRoot = $filesystemRoot;
28
	}
29
30
	/**
31
	 * Invoke auto update of music database after file or folder deletion
32
	 * @param Node $node pointing to the file or folder
33
	 */
34
	public static function deleted(Node $node) {
35
		$app = \OC::$server->query(Music::class);
36
		$container = $app->getContainer();
37
		$scanner = $container->query('Scanner');
38
39
		if ($node->getType() == FileInfo::TYPE_FILE) {
40
			$scanner->delete($node->getId());
41
		} else {
42
			$scanner->deleteFolder($node);
43
		}
44
	}
45
46
	/**
47
	 * Invoke auto update of music database after file update or file creation
48
	 * @param Node $node pointing to the file
49
	 */
50
	public static function updated(Node $node) {
51
		// At least on Nextcloud 13, it sometimes happens that this hook is triggered
52
		// when the core creates some temporary file and trying to access the provided
53
		// node throws an exception, probably because the temp file is already removed
54
		// by the time the execution gets here. See #636.
55
		// Furthermore, when the core opens a file in stream mode for writing using
56
		// File::fopen, this hook gets triggered immediately after the opening succeeds,
57
		// before anything is actually written and while the file is *exlusively locked
58
		// because of the write mode*. See #638.
59
		$app = \OC::$server->query(Music::class);
60
		$container = $app->getContainer();
61
		try {
62
			self::handleUpdated($node, $container);
63
		} catch (\OCP\Files\NotFoundException $e) {
64
			$logger = $container->query('Logger');
65
			$logger->log('FileHooks::updated triggered for a non-existing file', 'warn');
66
		} catch (\OCP\Lock\LockedException $e) {
67
			$logger = $container->query('Logger');
68
			$logger->log('FileHooks::updated triggered for a locked file ' . $node->getName(), 'warn');
69
		}
70
	}
71
72
	private static function handleUpdated(Node $node, IAppContainer $container) {
73
		// we are interested only about updates on files, not on folders
74
		if ($node->getType() == FileInfo::TYPE_FILE) {
75
			$scanner = $container->query('Scanner');
76
			$userId = $container->query('UserId');
77
78
			// When a file is uploaded to a folder shared by link, we end up here without current user.
79
			// In that case, fall back to using file owner
80
			if (empty($userId)) {
81
				$owner = $node->getOwner();
82
				$userId = $owner ? $owner->getUID() : null; // @phpstan-ignore-line At least some versions of NC may violate their PhpDoc and return null owner
0 ignored issues
show
introduced by
$owner is of type OCP\IUser, thus it always evaluated to true.
Loading history...
83
			}
84
85
			// Ignore event if we got no user or folder or the user has not yet scanned the music
86
			// collection. The last condition is especially to prevent problems when creating new user
87
			// and the default file set contains one or more audio files (see the discussion in #638).
88
			if (!empty($userId) && self::userHasMusicLib($userId, $container)) {
89
				$scanner->update($node, $userId, $node->getPath());
90
			}
91
		}
92
	}
93
94
	/**
95
	 * Check if user has any scanned tracks in his/her music library
96
	 * @param string $userId
97
	 * @param IAppContainer $container
98
	 */
99
	private static function userHasMusicLib(string $userId, IAppContainer $container) {
100
		$trackBusinessLayer = $container->query('TrackBusinessLayer');
101
		return 0 < $trackBusinessLayer->count($userId);
102
	}
103
104
	public static function preRenamed(Node $source, Node $target) {
105
		// We are interested only if the path of the folder of the file changes:
106
		// that could move a music file out of the scanned folder or remove a
107
		// cover image file from album folder.
108
		if ($source->getParent()->getId() != $target->getParent()->getId()) {
109
			self::deleted($source);
110
			// $target here doesn't point to an existing file, hence we need also
111
			// the postRenamed hook
112
		}
113
	}
114
115
	public static function postRenamed(Node $source, Node $target) {
116
		// Renaming/moving file may
117
		// a) move it into the folder scanned by Music app
118
		// b) have influence on the display name of the track
119
		// Both of these cases can be handled like file addition/modification.
120
		self::updated($target);
121
	}
122
123
	public function register() {
124
		$this->filesystemRoot->listen('\OC\Files', 'postWrite', [__CLASS__, 'updated']);
125
		$this->filesystemRoot->listen('\OC\Files', 'preDelete', [__CLASS__, 'deleted']);
126
		$this->filesystemRoot->listen('\OC\Files', 'preRename', [__CLASS__, 'preRenamed']);
127
		$this->filesystemRoot->listen('\OC\Files', 'postRename', [__CLASS__, 'postRenamed']);
128
	}
129
}
130