Passed
Push — master ( 8a5658...168ea4 )
by Pauli
03:36
created

FileHooks::safeExecute()   A

Complexity

Conditions 3
Paths 6

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 15
rs 10
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 - 2025
13
 */
14
15
namespace OCA\Music\Hooks;
16
17
use OCP\AppFramework\IAppContainer;
18
use OCP\Files\IRootFolder;
19
use OCP\Files\FileInfo;
20
use OCP\Files\Node;
21
22
use OCA\Music\AppInfo\Application;
23
24
class FileHooks {
25
	private IRootFolder $filesystemRoot;
26
27
	public function __construct(IRootFolder $filesystemRoot) {
28
		$this->filesystemRoot = $filesystemRoot;
29
	}
30
31
	/**
32
	 * Invoke auto update of music database after file or folder deletion
33
	 * @param Node $node pointing to the file or folder
34
	 */
35
	private static function deleted(Node $node) : void {
36
		$container = self::getContainer();
37
		$scanner = $container->query('Scanner');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

37
		$scanner = /** @scrutinizer ignore-deprecated */ $container->query('Scanner');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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
	private static function updated(Node $node) : void {
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 *exclusively locked
58
		// because of the write mode*. See #638.
59
		$container = self::getContainer();
60
		try {
61
			self::handleUpdated($node, $container);
62
		} catch (\OCP\Files\NotFoundException $e) {
63
			$logger = $container->query('Logger');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

63
			$logger = /** @scrutinizer ignore-deprecated */ $container->query('Logger');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
64
			$logger->log('FileHooks::updated triggered for a non-existing file', 'warn');
65
		} catch (\OCP\Lock\LockedException $e) {
66
			$logger = $container->query('Logger');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

66
			$logger = /** @scrutinizer ignore-deprecated */ $container->query('Logger');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
67
			$logger->log('FileHooks::updated triggered for a locked file ' . $node->getName(), 'warn');
68
		}
69
	}
70
71
	private static function handleUpdated(Node $node, IAppContainer $container) : void {
72
		// we are interested only about updates on files, not on folders
73
		if ($node->getType() == FileInfo::TYPE_FILE) {
74
			$scanner = $container->query('Scanner');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

74
			$scanner = /** @scrutinizer ignore-deprecated */ $container->query('Scanner');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
75
			$userId = self::getUser($node, $container);
76
77
			// Ignore event if we got no user or folder or the user has not yet scanned the music
78
			// collection. The last condition is especially to prevent problems when creating new user
79
			// and the default file set contains one or more audio files (see the discussion in #638).
80
			if (!empty($userId) && self::userHasMusicLib($userId, $container)) {
81
				$scanner->update($node, $userId, $node->getPath());
82
			}
83
		}
84
	}
85
86
	private static function moved(Node $node) : void {
87
		$container = self::getContainer();
88
		try {
89
			self::handleMoved($node, $container);
90
		} catch (\OCP\Files\NotFoundException $e) {
91
			$logger = $container->query('Logger');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

91
			$logger = /** @scrutinizer ignore-deprecated */ $container->query('Logger');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
92
			$logger->log('FileHooks::moved triggered for a non-existing file', 'warn');
93
		} catch (\OCP\Lock\LockedException $e) {
94
			$logger = $container->query('Logger');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

94
			$logger = /** @scrutinizer ignore-deprecated */ $container->query('Logger');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
95
			$logger->log('FileHooks::moved triggered for a locked file ' . $node->getName(), 'warn');
96
		}
97
	}
98
99
	private static function handleMoved(Node $node, IAppContainer $container) : void {
100
		$scanner = $container->query('Scanner');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

100
		$scanner = /** @scrutinizer ignore-deprecated */ $container->query('Scanner');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
101
		$userId = self::getUser($node, $container);
102
103
		if (!empty($userId) && self::userHasMusicLib($userId, $container)) {
104
			if ($node->getType() == FileInfo::TYPE_FILE) {
105
				$scanner->fileMoved($node, $userId);
106
			} else {
107
				$scanner->folderMoved($node, $userId);
108
			}
109
		}
110
	}
111
112
	private static function getUser(Node $node, IAppContainer $container) : ?string {
113
		$userId = $container->query('UserId');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

113
		$userId = /** @scrutinizer ignore-deprecated */ $container->query('UserId');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
114
115
		// When a file is uploaded to a folder shared by link, we end up here without current user.
116
		// In that case, fall back to using file owner
117
		if (empty($userId)) {
118
			// At least some versions of NC may violate their PhpDoc and return null owner, hence we need to aid PHPStan a bit about the type.
119
			/** @var \OCP\IUser|null $owner */
120
			$owner = $node->getOwner();
121
			$userId = $owner ? $owner->getUID() : null;
0 ignored issues
show
introduced by
$owner is of type OCP\IUser, thus it always evaluated to true.
Loading history...
122
		}
123
124
		return $userId;
125
	}
126
127
	private static function getContainer() : IAppContainer {
128
		$app = \OC::$server->query(Application::class);
129
		return $app->getContainer();
130
	}
131
132
	/**
133
	 * Check if user has any scanned tracks in his/her music library
134
	 * @param string $userId
135
	 * @param IAppContainer $container
136
	 */
137
	private static function userHasMusicLib(string $userId, IAppContainer $container) : bool {
138
		$trackBusinessLayer = $container->query('TrackBusinessLayer');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

138
		$trackBusinessLayer = /** @scrutinizer ignore-deprecated */ $container->query('TrackBusinessLayer');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
139
		return 0 < $trackBusinessLayer->count($userId);
140
	}
141
142
	private static function postRenamed(Node $source, Node $target) : void {
143
		// Beware: the $source describes the past state of the file and some of its functions will throw upon calling
144
145
		if ($source->getParent()->getId() != $target->getParent()->getId()) {
146
			self::moved($target);
147
		} else {
148
			self::updated($target);
149
		}
150
	}
151
152
	private static function safeExecute(callable $func) : void {
153
		// Don't let any exceptions or errors leak out of this method, no matter what unforeseen oddities happen.
154
		// We never want to prevent the actual file operation since our reactions to them are anyway non-crucial.
155
		// Especially during a server version update involving also Music app version update, the system may be
156
		// running a partially updated application version and that may lead to unexpected fatal errors, see
157
		// https://github.com/owncloud/music/issues/1231.
158
		try {
159
			try {
160
				$func();
161
			} catch (\Throwable $error) {
162
				$container = self::getContainer();
163
				$logger = $container->query('Logger');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\IContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

163
				$logger = /** @scrutinizer ignore-deprecated */ $container->query('Logger');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
164
				$logger->log("Error occurred while executing Music app file hook: {$error->getMessage()}. Stack trace: {$error->getTraceAsString()}", 'error');
165
			}
166
		} catch (\Throwable $error) {
167
			// even logging the error failed so just ignore
168
		}
169
	}
170
171
	public static function safeUpdated(Node $node) : void {
172
		self::safeExecute(fn() => self::updated($node));
173
	}
174
175
	public static function safeDeleted(Node $node) : void {
176
		self::safeExecute(fn() => self::deleted($node));
177
	}
178
179
	public static function safePostRenamed(Node $source, Node $target) : void {
180
		self::safeExecute(fn() => self::postRenamed($source, $target));
181
	}
182
183
	public function register() : void {
184
		$this->filesystemRoot->listen('\OC\Files', 'postWrite', [__CLASS__, 'safeUpdated']);
185
		$this->filesystemRoot->listen('\OC\Files', 'preDelete', [__CLASS__, 'safeDeleted']);
186
		$this->filesystemRoot->listen('\OC\Files', 'postRename', [__CLASS__, 'safePostRenamed']);
187
	}
188
}
189