Completed
Push — master ( 6c362c...3aa0c2 )
by
unknown
40:03
created
lib/private/Preview/Watcher.php 1 patch
Indentation   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -23,46 +23,46 @@
 block discarded – undo
23 23
  * Class that will watch filesystem activity and remove previews as needed.
24 24
  */
25 25
 class Watcher {
26
-	/**
27
-	 * Watcher constructor.
28
-	 */
29
-	public function __construct(
30
-		private readonly StorageFactory $storageFactory,
31
-		private readonly PreviewMapper $previewMapper,
32
-		private readonly IDBConnection $connection,
33
-	) {
34
-	}
26
+    /**
27
+     * Watcher constructor.
28
+     */
29
+    public function __construct(
30
+        private readonly StorageFactory $storageFactory,
31
+        private readonly PreviewMapper $previewMapper,
32
+        private readonly IDBConnection $connection,
33
+    ) {
34
+    }
35 35
 
36
-	public function postWrite(Node $node): void {
37
-		$this->deleteNode($node);
38
-	}
36
+    public function postWrite(Node $node): void {
37
+        $this->deleteNode($node);
38
+    }
39 39
 
40
-	protected function deleteNode(FileInfo $node): void {
41
-		// We only handle files
42
-		if ($node instanceof Folder) {
43
-			return;
44
-		}
40
+    protected function deleteNode(FileInfo $node): void {
41
+        // We only handle files
42
+        if ($node instanceof Folder) {
43
+            return;
44
+        }
45 45
 
46
-		$nodeId = $node->getId();
47
-		if (is_null($nodeId)) {
48
-			return;
49
-		}
46
+        $nodeId = $node->getId();
47
+        if (is_null($nodeId)) {
48
+            return;
49
+        }
50 50
 
51
-		[$node->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$nodeId]);
52
-		$this->connection->beginTransaction();
53
-		try {
54
-			foreach ($previews as $preview) {
55
-				$this->storageFactory->deletePreview($preview);
56
-				$this->previewMapper->delete($preview);
57
-			}
58
-		} finally {
59
-			$this->connection->commit();
60
-		}
61
-	}
51
+        [$node->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$nodeId]);
52
+        $this->connection->beginTransaction();
53
+        try {
54
+            foreach ($previews as $preview) {
55
+                $this->storageFactory->deletePreview($preview);
56
+                $this->previewMapper->delete($preview);
57
+            }
58
+        } finally {
59
+            $this->connection->commit();
60
+        }
61
+    }
62 62
 
63
-	public function versionRollback(array $data): void {
64
-		if (isset($data['node'])) {
65
-			$this->deleteNode($data['node']);
66
-		}
67
-	}
63
+    public function versionRollback(array $data): void {
64
+        if (isset($data['node'])) {
65
+            $this->deleteNode($data['node']);
66
+        }
67
+    }
68 68
 }
Please login to merge, or discard this patch.
lib/private/Preview/Db/PreviewMapper.php 2 patches
Indentation   +177 added lines, -177 removed lines patch added patch discarded remove patch
@@ -22,181 +22,181 @@
 block discarded – undo
22 22
  */
23 23
 class PreviewMapper extends QBMapper {
24 24
 
25
-	private const TABLE_NAME = 'previews';
26
-	private const LOCATION_TABLE_NAME = 'preview_locations';
27
-	private const VERSION_TABLE_NAME = 'preview_versions';
28
-
29
-	public function __construct(
30
-		IDBConnection $db,
31
-		private readonly IMimeTypeLoader $mimeTypeLoader,
32
-	) {
33
-		parent::__construct($db, self::TABLE_NAME, Preview::class);
34
-	}
35
-
36
-	protected function mapRowToEntity(array $row): Entity {
37
-		$row['mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['mimetype_id']);
38
-		$row['source_mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['source_mimetype_id']);
39
-
40
-		return parent::mapRowToEntity($row);
41
-	}
42
-
43
-	#[Override]
44
-	public function insert(Entity $entity): Entity {
45
-		/** @var Preview $preview */
46
-		$preview = $entity;
47
-
48
-		$preview->setMimetypeId($this->mimeTypeLoader->getId($preview->getMimeType()));
49
-		$preview->setSourceMimetypeId($this->mimeTypeLoader->getId($preview->getSourceMimeType()));
50
-
51
-		if ($preview->getVersion() !== null && $preview->getVersion() !== '') {
52
-			$qb = $this->db->getQueryBuilder();
53
-			$qb->insert(self::VERSION_TABLE_NAME)
54
-				->values([
55
-					'version' => $preview->getVersion(),
56
-					'file_id' => $preview->getFileId(),
57
-				])
58
-				->executeStatement();
59
-			$entity->setVersionId($qb->getLastInsertId());
60
-		}
61
-		return parent::insert($preview);
62
-	}
63
-
64
-	#[Override]
65
-	public function update(Entity $entity): Entity {
66
-		/** @var Preview $preview */
67
-		$preview = $entity;
68
-
69
-		$preview->setMimetypeId($this->mimeTypeLoader->getId($preview->getMimeType()));
70
-		$preview->setSourceMimetypeId($this->mimeTypeLoader->getId($preview->getSourceMimeType()));
71
-
72
-		return parent::update($preview);
73
-	}
74
-
75
-	#[Override]
76
-	public function delete(Entity $entity): Entity {
77
-		/** @var Preview $preview */
78
-		$preview = $entity;
79
-		if ($preview->getVersion() !== null && $preview->getVersion() !== '') {
80
-			$qb = $this->db->getQueryBuilder();
81
-			$qb->delete(self::VERSION_TABLE_NAME)
82
-				->where($qb->expr()->eq('file_id', $qb->createNamedParameter($preview->getFileId())))
83
-				->andWhere($qb->expr()->eq('version', $qb->createNamedParameter($preview->getVersion())))
84
-				->executeStatement();
85
-		}
86
-
87
-		return parent::delete($entity);
88
-	}
89
-
90
-	/**
91
-	 * @return \Generator<Preview>
92
-	 * @throws Exception
93
-	 */
94
-	public function getAvailablePreviewsForFile(int $fileId): \Generator {
95
-		$selectQb = $this->db->getQueryBuilder();
96
-		$this->joinLocation($selectQb)
97
-			->where($selectQb->expr()->eq('p.file_id', $selectQb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
98
-		yield from $this->yieldEntities($selectQb);
99
-	}
100
-
101
-	/**
102
-	 * @param int[] $fileIds
103
-	 * @return array<int, Preview[]>
104
-	 * @throws Exception
105
-	 */
106
-	public function getAvailablePreviews(array $fileIds): array {
107
-		$selectQb = $this->db->getQueryBuilder();
108
-		$this->joinLocation($selectQb)
109
-			->where(
110
-				$selectQb->expr()->in('p.file_id', $selectQb->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY)),
111
-			);
112
-		$previews = array_fill_keys($fileIds, []);
113
-		foreach ($this->yieldEntities($selectQb) as $preview) {
114
-			$previews[$preview->getFileId()][] = $preview;
115
-		}
116
-		return $previews;
117
-	}
118
-
119
-	/**
120
-	 * @return \Generator<Preview>
121
-	 */
122
-	public function getByFileId(int $fileId): \Generator {
123
-		$selectQb = $this->db->getQueryBuilder();
124
-		$this->joinLocation($selectQb)
125
-			->where($selectQb->expr()->eq('file_id', $selectQb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
126
-		yield from $this->yieldEntities($selectQb);
127
-	}
128
-
129
-	/**
130
-	 * @param int[] $previewIds
131
-	 */
132
-	public function deleteByIds(array $previewIds): void {
133
-		$qb = $this->db->getQueryBuilder();
134
-		$qb->delete(self::TABLE_NAME)
135
-			->where($qb->expr()->andX(
136
-				$qb->expr()->in('id', $qb->createNamedParameter($previewIds, IQueryBuilder::PARAM_INT_ARRAY))
137
-			))->executeStatement();
138
-	}
139
-
140
-	protected function joinLocation(IQueryBuilder $qb): IQueryBuilder {
141
-		return $qb->select('p.*', 'l.bucket_name', 'l.object_store_name', 'v.version')
142
-			->from(self::TABLE_NAME, 'p')
143
-			->leftJoin('p', self::LOCATION_TABLE_NAME, 'l', $qb->expr()->eq(
144
-				'p.location_id', 'l.id'
145
-			))
146
-			->leftJoin('p', self::VERSION_TABLE_NAME, 'v', $qb->expr()->eq(
147
-				'p.version_id', 'v.id'
148
-			));
149
-	}
150
-
151
-	public function getLocationId(string $bucket, string $objectStore): int {
152
-		$qb = $this->db->getQueryBuilder();
153
-		$result = $qb->select('id')
154
-			->from(self::LOCATION_TABLE_NAME)
155
-			->where($qb->expr()->eq('bucket_name', $qb->createNamedParameter($bucket)))
156
-			->andWhere($qb->expr()->eq('object_store_name', $qb->createNamedParameter($objectStore)))
157
-			->executeQuery();
158
-		$data = $result->fetchOne();
159
-		if ($data) {
160
-			return $data;
161
-		} else {
162
-			$qb->insert(self::LOCATION_TABLE_NAME)
163
-				->values([
164
-					'bucket_name' => $qb->createNamedParameter($bucket),
165
-					'object_store_name' => $qb->createNamedParameter($objectStore),
166
-				])->executeStatement();
167
-			return $qb->getLastInsertId();
168
-		}
169
-	}
170
-
171
-	public function deleteAll(): void {
172
-		$delete = $this->db->getQueryBuilder();
173
-		$delete->delete($this->getTableName());
174
-	}
175
-
176
-	/**
177
-	 * @return \Generator<Preview>
178
-	 */
179
-	public function getPreviews(int $lastId, int $limit = 1000): \Generator {
180
-		$qb = $this->db->getQueryBuilder();
181
-		$this->joinLocation($qb)
182
-			->where($qb->expr()->gt('p.id', $qb->createNamedParameter($lastId, IQueryBuilder::PARAM_INT)))
183
-			->setMaxResults($limit);
184
-		return $this->yieldEntities($qb);
185
-
186
-	}
187
-
188
-	/**
189
-	 * @param string[] $mimeTypes
190
-	 * @return \Generator<Preview>
191
-	 */
192
-	public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
193
-		$qb = $this->db->getQueryBuilder();
194
-		$this->joinLocation($qb)
195
-			->where($qb->expr()->orX(
196
-				...array_map(function (string $mimeType) use ($qb): string {
197
-					return $qb->expr()->eq('source_mimetype_id', $qb->createNamedParameter($this->mimeTypeLoader->getId($mimeType), IQueryBuilder::PARAM_INT));
198
-				}, $mimeTypes)
199
-			));
200
-		return $this->yieldEntities($qb);
201
-	}
25
+    private const TABLE_NAME = 'previews';
26
+    private const LOCATION_TABLE_NAME = 'preview_locations';
27
+    private const VERSION_TABLE_NAME = 'preview_versions';
28
+
29
+    public function __construct(
30
+        IDBConnection $db,
31
+        private readonly IMimeTypeLoader $mimeTypeLoader,
32
+    ) {
33
+        parent::__construct($db, self::TABLE_NAME, Preview::class);
34
+    }
35
+
36
+    protected function mapRowToEntity(array $row): Entity {
37
+        $row['mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['mimetype_id']);
38
+        $row['source_mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['source_mimetype_id']);
39
+
40
+        return parent::mapRowToEntity($row);
41
+    }
42
+
43
+    #[Override]
44
+    public function insert(Entity $entity): Entity {
45
+        /** @var Preview $preview */
46
+        $preview = $entity;
47
+
48
+        $preview->setMimetypeId($this->mimeTypeLoader->getId($preview->getMimeType()));
49
+        $preview->setSourceMimetypeId($this->mimeTypeLoader->getId($preview->getSourceMimeType()));
50
+
51
+        if ($preview->getVersion() !== null && $preview->getVersion() !== '') {
52
+            $qb = $this->db->getQueryBuilder();
53
+            $qb->insert(self::VERSION_TABLE_NAME)
54
+                ->values([
55
+                    'version' => $preview->getVersion(),
56
+                    'file_id' => $preview->getFileId(),
57
+                ])
58
+                ->executeStatement();
59
+            $entity->setVersionId($qb->getLastInsertId());
60
+        }
61
+        return parent::insert($preview);
62
+    }
63
+
64
+    #[Override]
65
+    public function update(Entity $entity): Entity {
66
+        /** @var Preview $preview */
67
+        $preview = $entity;
68
+
69
+        $preview->setMimetypeId($this->mimeTypeLoader->getId($preview->getMimeType()));
70
+        $preview->setSourceMimetypeId($this->mimeTypeLoader->getId($preview->getSourceMimeType()));
71
+
72
+        return parent::update($preview);
73
+    }
74
+
75
+    #[Override]
76
+    public function delete(Entity $entity): Entity {
77
+        /** @var Preview $preview */
78
+        $preview = $entity;
79
+        if ($preview->getVersion() !== null && $preview->getVersion() !== '') {
80
+            $qb = $this->db->getQueryBuilder();
81
+            $qb->delete(self::VERSION_TABLE_NAME)
82
+                ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($preview->getFileId())))
83
+                ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter($preview->getVersion())))
84
+                ->executeStatement();
85
+        }
86
+
87
+        return parent::delete($entity);
88
+    }
89
+
90
+    /**
91
+     * @return \Generator<Preview>
92
+     * @throws Exception
93
+     */
94
+    public function getAvailablePreviewsForFile(int $fileId): \Generator {
95
+        $selectQb = $this->db->getQueryBuilder();
96
+        $this->joinLocation($selectQb)
97
+            ->where($selectQb->expr()->eq('p.file_id', $selectQb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
98
+        yield from $this->yieldEntities($selectQb);
99
+    }
100
+
101
+    /**
102
+     * @param int[] $fileIds
103
+     * @return array<int, Preview[]>
104
+     * @throws Exception
105
+     */
106
+    public function getAvailablePreviews(array $fileIds): array {
107
+        $selectQb = $this->db->getQueryBuilder();
108
+        $this->joinLocation($selectQb)
109
+            ->where(
110
+                $selectQb->expr()->in('p.file_id', $selectQb->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY)),
111
+            );
112
+        $previews = array_fill_keys($fileIds, []);
113
+        foreach ($this->yieldEntities($selectQb) as $preview) {
114
+            $previews[$preview->getFileId()][] = $preview;
115
+        }
116
+        return $previews;
117
+    }
118
+
119
+    /**
120
+     * @return \Generator<Preview>
121
+     */
122
+    public function getByFileId(int $fileId): \Generator {
123
+        $selectQb = $this->db->getQueryBuilder();
124
+        $this->joinLocation($selectQb)
125
+            ->where($selectQb->expr()->eq('file_id', $selectQb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
126
+        yield from $this->yieldEntities($selectQb);
127
+    }
128
+
129
+    /**
130
+     * @param int[] $previewIds
131
+     */
132
+    public function deleteByIds(array $previewIds): void {
133
+        $qb = $this->db->getQueryBuilder();
134
+        $qb->delete(self::TABLE_NAME)
135
+            ->where($qb->expr()->andX(
136
+                $qb->expr()->in('id', $qb->createNamedParameter($previewIds, IQueryBuilder::PARAM_INT_ARRAY))
137
+            ))->executeStatement();
138
+    }
139
+
140
+    protected function joinLocation(IQueryBuilder $qb): IQueryBuilder {
141
+        return $qb->select('p.*', 'l.bucket_name', 'l.object_store_name', 'v.version')
142
+            ->from(self::TABLE_NAME, 'p')
143
+            ->leftJoin('p', self::LOCATION_TABLE_NAME, 'l', $qb->expr()->eq(
144
+                'p.location_id', 'l.id'
145
+            ))
146
+            ->leftJoin('p', self::VERSION_TABLE_NAME, 'v', $qb->expr()->eq(
147
+                'p.version_id', 'v.id'
148
+            ));
149
+    }
150
+
151
+    public function getLocationId(string $bucket, string $objectStore): int {
152
+        $qb = $this->db->getQueryBuilder();
153
+        $result = $qb->select('id')
154
+            ->from(self::LOCATION_TABLE_NAME)
155
+            ->where($qb->expr()->eq('bucket_name', $qb->createNamedParameter($bucket)))
156
+            ->andWhere($qb->expr()->eq('object_store_name', $qb->createNamedParameter($objectStore)))
157
+            ->executeQuery();
158
+        $data = $result->fetchOne();
159
+        if ($data) {
160
+            return $data;
161
+        } else {
162
+            $qb->insert(self::LOCATION_TABLE_NAME)
163
+                ->values([
164
+                    'bucket_name' => $qb->createNamedParameter($bucket),
165
+                    'object_store_name' => $qb->createNamedParameter($objectStore),
166
+                ])->executeStatement();
167
+            return $qb->getLastInsertId();
168
+        }
169
+    }
170
+
171
+    public function deleteAll(): void {
172
+        $delete = $this->db->getQueryBuilder();
173
+        $delete->delete($this->getTableName());
174
+    }
175
+
176
+    /**
177
+     * @return \Generator<Preview>
178
+     */
179
+    public function getPreviews(int $lastId, int $limit = 1000): \Generator {
180
+        $qb = $this->db->getQueryBuilder();
181
+        $this->joinLocation($qb)
182
+            ->where($qb->expr()->gt('p.id', $qb->createNamedParameter($lastId, IQueryBuilder::PARAM_INT)))
183
+            ->setMaxResults($limit);
184
+        return $this->yieldEntities($qb);
185
+
186
+    }
187
+
188
+    /**
189
+     * @param string[] $mimeTypes
190
+     * @return \Generator<Preview>
191
+     */
192
+    public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
193
+        $qb = $this->db->getQueryBuilder();
194
+        $this->joinLocation($qb)
195
+            ->where($qb->expr()->orX(
196
+                ...array_map(function (string $mimeType) use ($qb): string {
197
+                    return $qb->expr()->eq('source_mimetype_id', $qb->createNamedParameter($this->mimeTypeLoader->getId($mimeType), IQueryBuilder::PARAM_INT));
198
+                }, $mimeTypes)
199
+            ));
200
+        return $this->yieldEntities($qb);
201
+    }
202 202
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -34,8 +34,8 @@  discard block
 block discarded – undo
34 34
 	}
35 35
 
36 36
 	protected function mapRowToEntity(array $row): Entity {
37
-		$row['mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['mimetype_id']);
38
-		$row['source_mimetype'] = $this->mimeTypeLoader->getMimetypeById((int)$row['source_mimetype_id']);
37
+		$row['mimetype'] = $this->mimeTypeLoader->getMimetypeById((int) $row['mimetype_id']);
38
+		$row['source_mimetype'] = $this->mimeTypeLoader->getMimetypeById((int) $row['source_mimetype_id']);
39 39
 
40 40
 		return parent::mapRowToEntity($row);
41 41
 	}
@@ -193,7 +193,7 @@  discard block
 block discarded – undo
193 193
 		$qb = $this->db->getQueryBuilder();
194 194
 		$this->joinLocation($qb)
195 195
 			->where($qb->expr()->orX(
196
-				...array_map(function (string $mimeType) use ($qb): string {
196
+				...array_map(function(string $mimeType) use ($qb): string {
197 197
 					return $qb->expr()->eq('source_mimetype_id', $qb->createNamedParameter($this->mimeTypeLoader->getId($mimeType), IQueryBuilder::PARAM_INT));
198 198
 				}, $mimeTypes)
199 199
 			));
Please login to merge, or discard this patch.
lib/private/Preview/Db/Preview.php 2 patches
Indentation   +122 added lines, -122 removed lines patch added patch discarded remove patch
@@ -54,126 +54,126 @@
 block discarded – undo
54 54
  * @see PreviewMapper
55 55
  */
56 56
 class Preview extends Entity {
57
-	protected ?int $fileId = null;
58
-	protected ?int $oldFileId = null;
59
-	protected ?int $storageId = null;
60
-	protected ?int $locationId = null;
61
-	protected ?string $bucketName = null;
62
-	protected ?string $objectStoreName = null;
63
-	protected ?int $width = null;
64
-	protected ?int $height = null;
65
-	protected ?int $mimetypeId = null;
66
-	protected ?int $sourceMimetypeId = null;
67
-	protected string $mimetype = 'application/octet-stream';
68
-	protected string $sourceMimetype = 'application/octet-stream';
69
-	protected ?int $mtime = null;
70
-	protected ?int $size = null;
71
-	protected ?bool $max = null;
72
-	protected ?bool $cropped = null;
73
-	protected ?string $etag = null;
74
-	protected ?string $version = null;
75
-	protected ?int $versionId = null;
76
-	protected ?bool $encrypted = null;
77
-
78
-	public function __construct() {
79
-		$this->addType('fileId', Types::BIGINT);
80
-		$this->addType('storageId', Types::BIGINT);
81
-		$this->addType('oldFileId', Types::BIGINT);
82
-		$this->addType('locationId', Types::BIGINT);
83
-		$this->addType('width', Types::INTEGER);
84
-		$this->addType('height', Types::INTEGER);
85
-		$this->addType('mimetypeId', Types::INTEGER);
86
-		$this->addType('sourceMimetypeId', Types::INTEGER);
87
-		$this->addType('mtime', Types::INTEGER);
88
-		$this->addType('size', Types::INTEGER);
89
-		$this->addType('max', Types::BOOLEAN);
90
-		$this->addType('cropped', Types::BOOLEAN);
91
-		$this->addType('encrypted', Types::BOOLEAN);
92
-		$this->addType('etag', Types::STRING);
93
-		$this->addType('versionId', Types::STRING);
94
-	}
95
-
96
-	public static function fromPath(string $path, IMimeTypeDetector $mimeTypeDetector): Preview|false {
97
-		$preview = new self();
98
-		$preview->setFileId((int)basename(dirname($path)));
99
-
100
-		$fileName = pathinfo($path, PATHINFO_FILENAME) . '.' . pathinfo($path, PATHINFO_EXTENSION);
101
-		$ok = preg_match('/(([A-Za-z0-9\+\/]+)-)?([0-9]+)-([0-9]+)(-(max))?(-(crop))?\.([a-z]{3,4})/', $fileName, $matches);
102
-
103
-		if ($ok !== 1) {
104
-			return false;
105
-		}
106
-
107
-		[
108
-			2 => $version,
109
-			3 => $width,
110
-			4 => $height,
111
-			6 => $max,
112
-			8 => $crop,
113
-		] = $matches;
114
-
115
-		$preview->setMimeType($mimeTypeDetector->detectPath($fileName));
116
-
117
-		$preview->setWidth((int)$width);
118
-		$preview->setHeight((int)$height);
119
-		$preview->setCropped($crop === 'crop');
120
-		$preview->setMax($max === 'max');
121
-
122
-		if (!empty($version)) {
123
-			$preview->setVersion($version);
124
-		}
125
-		return $preview;
126
-	}
127
-
128
-	public function getName(): string {
129
-		$path = ($this->getVersion() > -1 ? $this->getVersion() . '-' : '') . $this->getWidth() . '-' . $this->getHeight();
130
-		if ($this->isCropped()) {
131
-			$path .= '-crop';
132
-		}
133
-		if ($this->isMax()) {
134
-			$path .= '-max';
135
-		}
136
-
137
-		$ext = $this->getExtension();
138
-		$path .= '.' . $ext;
139
-		return $path;
140
-	}
141
-
142
-	public function getExtension(): string {
143
-		return match ($this->getMimeType()) {
144
-			'image/png' => 'png',
145
-			'image/gif' => 'gif',
146
-			'image/jpeg' => 'jpg',
147
-			'image/webp' => 'webp',
148
-			default => 'png',
149
-		};
150
-	}
151
-
152
-	public function setBucketName(string $bucketName): void {
153
-		$this->bucketName = $bucketName;
154
-	}
155
-
156
-	public function setObjectStoreName(string $objectStoreName): void {
157
-		$this->objectStoreName = $objectStoreName;
158
-	}
159
-
160
-	public function setVersion(?string $version): void {
161
-		$this->version = $version;
162
-	}
163
-
164
-	public function getMimeType(): string {
165
-		return $this->mimetype;
166
-	}
167
-
168
-	public function setMimeType(string $mimeType): void {
169
-		$this->mimetype = $mimeType;
170
-	}
171
-
172
-	public function getSourceMimeType(): string {
173
-		return $this->sourceMimetype;
174
-	}
175
-
176
-	public function setSourceMimeType(string $mimeType): void {
177
-		$this->sourceMimetype = $mimeType;
178
-	}
57
+    protected ?int $fileId = null;
58
+    protected ?int $oldFileId = null;
59
+    protected ?int $storageId = null;
60
+    protected ?int $locationId = null;
61
+    protected ?string $bucketName = null;
62
+    protected ?string $objectStoreName = null;
63
+    protected ?int $width = null;
64
+    protected ?int $height = null;
65
+    protected ?int $mimetypeId = null;
66
+    protected ?int $sourceMimetypeId = null;
67
+    protected string $mimetype = 'application/octet-stream';
68
+    protected string $sourceMimetype = 'application/octet-stream';
69
+    protected ?int $mtime = null;
70
+    protected ?int $size = null;
71
+    protected ?bool $max = null;
72
+    protected ?bool $cropped = null;
73
+    protected ?string $etag = null;
74
+    protected ?string $version = null;
75
+    protected ?int $versionId = null;
76
+    protected ?bool $encrypted = null;
77
+
78
+    public function __construct() {
79
+        $this->addType('fileId', Types::BIGINT);
80
+        $this->addType('storageId', Types::BIGINT);
81
+        $this->addType('oldFileId', Types::BIGINT);
82
+        $this->addType('locationId', Types::BIGINT);
83
+        $this->addType('width', Types::INTEGER);
84
+        $this->addType('height', Types::INTEGER);
85
+        $this->addType('mimetypeId', Types::INTEGER);
86
+        $this->addType('sourceMimetypeId', Types::INTEGER);
87
+        $this->addType('mtime', Types::INTEGER);
88
+        $this->addType('size', Types::INTEGER);
89
+        $this->addType('max', Types::BOOLEAN);
90
+        $this->addType('cropped', Types::BOOLEAN);
91
+        $this->addType('encrypted', Types::BOOLEAN);
92
+        $this->addType('etag', Types::STRING);
93
+        $this->addType('versionId', Types::STRING);
94
+    }
95
+
96
+    public static function fromPath(string $path, IMimeTypeDetector $mimeTypeDetector): Preview|false {
97
+        $preview = new self();
98
+        $preview->setFileId((int)basename(dirname($path)));
99
+
100
+        $fileName = pathinfo($path, PATHINFO_FILENAME) . '.' . pathinfo($path, PATHINFO_EXTENSION);
101
+        $ok = preg_match('/(([A-Za-z0-9\+\/]+)-)?([0-9]+)-([0-9]+)(-(max))?(-(crop))?\.([a-z]{3,4})/', $fileName, $matches);
102
+
103
+        if ($ok !== 1) {
104
+            return false;
105
+        }
106
+
107
+        [
108
+            2 => $version,
109
+            3 => $width,
110
+            4 => $height,
111
+            6 => $max,
112
+            8 => $crop,
113
+        ] = $matches;
114
+
115
+        $preview->setMimeType($mimeTypeDetector->detectPath($fileName));
116
+
117
+        $preview->setWidth((int)$width);
118
+        $preview->setHeight((int)$height);
119
+        $preview->setCropped($crop === 'crop');
120
+        $preview->setMax($max === 'max');
121
+
122
+        if (!empty($version)) {
123
+            $preview->setVersion($version);
124
+        }
125
+        return $preview;
126
+    }
127
+
128
+    public function getName(): string {
129
+        $path = ($this->getVersion() > -1 ? $this->getVersion() . '-' : '') . $this->getWidth() . '-' . $this->getHeight();
130
+        if ($this->isCropped()) {
131
+            $path .= '-crop';
132
+        }
133
+        if ($this->isMax()) {
134
+            $path .= '-max';
135
+        }
136
+
137
+        $ext = $this->getExtension();
138
+        $path .= '.' . $ext;
139
+        return $path;
140
+    }
141
+
142
+    public function getExtension(): string {
143
+        return match ($this->getMimeType()) {
144
+            'image/png' => 'png',
145
+            'image/gif' => 'gif',
146
+            'image/jpeg' => 'jpg',
147
+            'image/webp' => 'webp',
148
+            default => 'png',
149
+        };
150
+    }
151
+
152
+    public function setBucketName(string $bucketName): void {
153
+        $this->bucketName = $bucketName;
154
+    }
155
+
156
+    public function setObjectStoreName(string $objectStoreName): void {
157
+        $this->objectStoreName = $objectStoreName;
158
+    }
159
+
160
+    public function setVersion(?string $version): void {
161
+        $this->version = $version;
162
+    }
163
+
164
+    public function getMimeType(): string {
165
+        return $this->mimetype;
166
+    }
167
+
168
+    public function setMimeType(string $mimeType): void {
169
+        $this->mimetype = $mimeType;
170
+    }
171
+
172
+    public function getSourceMimeType(): string {
173
+        return $this->sourceMimetype;
174
+    }
175
+
176
+    public function setSourceMimeType(string $mimeType): void {
177
+        $this->sourceMimetype = $mimeType;
178
+    }
179 179
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -93,11 +93,11 @@  discard block
 block discarded – undo
93 93
 		$this->addType('versionId', Types::STRING);
94 94
 	}
95 95
 
96
-	public static function fromPath(string $path, IMimeTypeDetector $mimeTypeDetector): Preview|false {
96
+	public static function fromPath(string $path, IMimeTypeDetector $mimeTypeDetector): Preview | false {
97 97
 		$preview = new self();
98
-		$preview->setFileId((int)basename(dirname($path)));
98
+		$preview->setFileId((int) basename(dirname($path)));
99 99
 
100
-		$fileName = pathinfo($path, PATHINFO_FILENAME) . '.' . pathinfo($path, PATHINFO_EXTENSION);
100
+		$fileName = pathinfo($path, PATHINFO_FILENAME).'.'.pathinfo($path, PATHINFO_EXTENSION);
101 101
 		$ok = preg_match('/(([A-Za-z0-9\+\/]+)-)?([0-9]+)-([0-9]+)(-(max))?(-(crop))?\.([a-z]{3,4})/', $fileName, $matches);
102 102
 
103 103
 		if ($ok !== 1) {
@@ -114,8 +114,8 @@  discard block
 block discarded – undo
114 114
 
115 115
 		$preview->setMimeType($mimeTypeDetector->detectPath($fileName));
116 116
 
117
-		$preview->setWidth((int)$width);
118
-		$preview->setHeight((int)$height);
117
+		$preview->setWidth((int) $width);
118
+		$preview->setHeight((int) $height);
119 119
 		$preview->setCropped($crop === 'crop');
120 120
 		$preview->setMax($max === 'max');
121 121
 
@@ -126,7 +126,7 @@  discard block
 block discarded – undo
126 126
 	}
127 127
 
128 128
 	public function getName(): string {
129
-		$path = ($this->getVersion() > -1 ? $this->getVersion() . '-' : '') . $this->getWidth() . '-' . $this->getHeight();
129
+		$path = ($this->getVersion() > -1 ? $this->getVersion().'-' : '').$this->getWidth().'-'.$this->getHeight();
130 130
 		if ($this->isCropped()) {
131 131
 			$path .= '-crop';
132 132
 		}
@@ -135,7 +135,7 @@  discard block
 block discarded – undo
135 135
 		}
136 136
 
137 137
 		$ext = $this->getExtension();
138
-		$path .= '.' . $ext;
138
+		$path .= '.'.$ext;
139 139
 		return $path;
140 140
 	}
141 141
 
Please login to merge, or discard this patch.
lib/private/Preview/PreviewService.php 2 patches
Indentation   +90 added lines, -90 removed lines patch added patch discarded remove patch
@@ -16,94 +16,94 @@
 block discarded – undo
16 16
 use OCP\IDBConnection;
17 17
 
18 18
 class PreviewService {
19
-	public function __construct(
20
-		private readonly StorageFactory $storageFactory,
21
-		private readonly PreviewMapper $previewMapper,
22
-		private readonly IDBConnection $connection,
23
-	) {
24
-	}
25
-
26
-	public function deletePreview(Preview $preview): void {
27
-		$this->storageFactory->deletePreview($preview);
28
-		$this->previewMapper->delete($preview);
29
-	}
30
-
31
-	/**
32
-	 * Get storageId and fileIds for which we have at least one preview.
33
-	 *
34
-	 * @return \Generator<array{storageId: int, fileIds: int[]}>
35
-	 */
36
-	public function getAvailableFileIds(): \Generator {
37
-		$maxQb = $this->connection->getQueryBuilder();
38
-		$maxQb->select($maxQb->func()->max('id'))
39
-			->from($this->previewMapper->getTableName())
40
-			->groupBy('file_id');
41
-
42
-		$qb = $this->connection->getQueryBuilder();
43
-		$qb->select('file_id', 'storage_id')
44
-			->from($this->previewMapper->getTableName())
45
-			->where($qb->expr()->in('id', $qb->createFunction($maxQb->getSQL())));
46
-
47
-		$result = $qb->executeQuery();
48
-
49
-		$lastStorageId = -1;
50
-		/** @var int[] $fileIds */
51
-		$fileIds = [];
52
-
53
-		// Previews next to each others in the database are likely in the same storage, so group them
54
-		while ($row = $result->fetch()) {
55
-			if ($lastStorageId !== $row['storage_id']) {
56
-				if ($lastStorageId !== -1) {
57
-					yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
58
-					$fileIds = [];
59
-				}
60
-				$lastStorageId = (int)$row['storage_id'];
61
-			}
62
-			$fileIds[] = (int)$row['file_id'];
63
-		}
64
-
65
-		if (count($fileIds) > 0) {
66
-			yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
67
-		}
68
-	}
69
-
70
-	/**
71
-	 * @return \Generator<Preview>
72
-	 */
73
-	public function getAvailablePreviewsForFile(int $fileId): \Generator {
74
-		return $this->previewMapper->getAvailablePreviewsForFile($fileId);
75
-	}
76
-
77
-	/**
78
-	 * @param string[] $mimeTypes
79
-	 * @return \Generator<Preview>
80
-	 */
81
-	public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
82
-		return $this->previewMapper->getPreviewsForMimeTypes($mimeTypes);
83
-	}
84
-
85
-	public function deleteAll(): void {
86
-		$lastId = 0;
87
-		while (true) {
88
-			$previews = $this->previewMapper->getPreviews($lastId, 1000);
89
-			$i = 0;
90
-			foreach ($previews as $preview) {
91
-				$this->deletePreview($preview);
92
-				$i++;
93
-				$lastId = $preview->getId();
94
-			}
95
-
96
-			if ($i !== 1000) {
97
-				break;
98
-			}
99
-		}
100
-	}
101
-
102
-	/**
103
-	 * @param int[] $fileIds
104
-	 * @return array<int, Preview[]>
105
-	 */
106
-	public function getAvailablePreviews(array $fileIds): array {
107
-		return $this->previewMapper->getAvailablePreviews($fileIds);
108
-	}
19
+    public function __construct(
20
+        private readonly StorageFactory $storageFactory,
21
+        private readonly PreviewMapper $previewMapper,
22
+        private readonly IDBConnection $connection,
23
+    ) {
24
+    }
25
+
26
+    public function deletePreview(Preview $preview): void {
27
+        $this->storageFactory->deletePreview($preview);
28
+        $this->previewMapper->delete($preview);
29
+    }
30
+
31
+    /**
32
+     * Get storageId and fileIds for which we have at least one preview.
33
+     *
34
+     * @return \Generator<array{storageId: int, fileIds: int[]}>
35
+     */
36
+    public function getAvailableFileIds(): \Generator {
37
+        $maxQb = $this->connection->getQueryBuilder();
38
+        $maxQb->select($maxQb->func()->max('id'))
39
+            ->from($this->previewMapper->getTableName())
40
+            ->groupBy('file_id');
41
+
42
+        $qb = $this->connection->getQueryBuilder();
43
+        $qb->select('file_id', 'storage_id')
44
+            ->from($this->previewMapper->getTableName())
45
+            ->where($qb->expr()->in('id', $qb->createFunction($maxQb->getSQL())));
46
+
47
+        $result = $qb->executeQuery();
48
+
49
+        $lastStorageId = -1;
50
+        /** @var int[] $fileIds */
51
+        $fileIds = [];
52
+
53
+        // Previews next to each others in the database are likely in the same storage, so group them
54
+        while ($row = $result->fetch()) {
55
+            if ($lastStorageId !== $row['storage_id']) {
56
+                if ($lastStorageId !== -1) {
57
+                    yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
58
+                    $fileIds = [];
59
+                }
60
+                $lastStorageId = (int)$row['storage_id'];
61
+            }
62
+            $fileIds[] = (int)$row['file_id'];
63
+        }
64
+
65
+        if (count($fileIds) > 0) {
66
+            yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
67
+        }
68
+    }
69
+
70
+    /**
71
+     * @return \Generator<Preview>
72
+     */
73
+    public function getAvailablePreviewsForFile(int $fileId): \Generator {
74
+        return $this->previewMapper->getAvailablePreviewsForFile($fileId);
75
+    }
76
+
77
+    /**
78
+     * @param string[] $mimeTypes
79
+     * @return \Generator<Preview>
80
+     */
81
+    public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
82
+        return $this->previewMapper->getPreviewsForMimeTypes($mimeTypes);
83
+    }
84
+
85
+    public function deleteAll(): void {
86
+        $lastId = 0;
87
+        while (true) {
88
+            $previews = $this->previewMapper->getPreviews($lastId, 1000);
89
+            $i = 0;
90
+            foreach ($previews as $preview) {
91
+                $this->deletePreview($preview);
92
+                $i++;
93
+                $lastId = $preview->getId();
94
+            }
95
+
96
+            if ($i !== 1000) {
97
+                break;
98
+            }
99
+        }
100
+    }
101
+
102
+    /**
103
+     * @param int[] $fileIds
104
+     * @return array<int, Preview[]>
105
+     */
106
+    public function getAvailablePreviews(array $fileIds): array {
107
+        return $this->previewMapper->getAvailablePreviews($fileIds);
108
+    }
109 109
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -57,9 +57,9 @@
 block discarded – undo
57 57
 					yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
58 58
 					$fileIds = [];
59 59
 				}
60
-				$lastStorageId = (int)$row['storage_id'];
60
+				$lastStorageId = (int) $row['storage_id'];
61 61
 			}
62
-			$fileIds[] = (int)$row['file_id'];
62
+			$fileIds[] = (int) $row['file_id'];
63 63
 		}
64 64
 
65 65
 		if (count($fileIds) > 0) {
Please login to merge, or discard this patch.
lib/private/Preview/BackgroundCleanupJob.php 2 patches
Indentation   +64 added lines, -64 removed lines patch added patch discarded remove patch
@@ -20,42 +20,42 @@  discard block
 block discarded – undo
20 20
  */
21 21
 class BackgroundCleanupJob extends TimedJob {
22 22
 
23
-	public function __construct(
24
-		ITimeFactory $timeFactory,
25
-		readonly private IDBConnection $connection,
26
-		readonly private PreviewService $previewService,
27
-		readonly private bool $isCLI,
28
-	) {
29
-		parent::__construct($timeFactory);
30
-		// Run at most once an hour
31
-		$this->setInterval(60 * 60);
32
-		$this->setTimeSensitivity(self::TIME_INSENSITIVE);
33
-	}
23
+    public function __construct(
24
+        ITimeFactory $timeFactory,
25
+        readonly private IDBConnection $connection,
26
+        readonly private PreviewService $previewService,
27
+        readonly private bool $isCLI,
28
+    ) {
29
+        parent::__construct($timeFactory);
30
+        // Run at most once an hour
31
+        $this->setInterval(60 * 60);
32
+        $this->setTimeSensitivity(self::TIME_INSENSITIVE);
33
+    }
34 34
 
35
-	public function run($argument): void {
36
-		foreach ($this->getDeletedFiles() as $fileId) {
37
-			$previewIds = [];
38
-			foreach ($this->previewService->getAvailablePreviewsForFile($fileId) as $preview) {
39
-				$this->previewService->deletePreview($preview);
40
-			}
41
-		}
42
-	}
35
+    public function run($argument): void {
36
+        foreach ($this->getDeletedFiles() as $fileId) {
37
+            $previewIds = [];
38
+            foreach ($this->previewService->getAvailablePreviewsForFile($fileId) as $preview) {
39
+                $this->previewService->deletePreview($preview);
40
+            }
41
+        }
42
+    }
43 43
 
44
-	/**
45
-	 * @return \Iterator<FileId>
46
-	 */
47
-	private function getDeletedFiles(): \Iterator {
48
-		if ($this->connection->getShardDefinition('filecache')) {
49
-			foreach ($this->previewService->getAvailableFileIds() as $availableFileIdGroup) {
50
-				$fileIds = $this->findMissingSources($availableFileIdGroup['storageId'], $availableFileIdGroup['fileIds']);
51
-				foreach ($fileIds as $fileId) {
52
-					yield $fileId;
53
-				}
54
-			}
55
-			return;
56
-		}
44
+    /**
45
+     * @return \Iterator<FileId>
46
+     */
47
+    private function getDeletedFiles(): \Iterator {
48
+        if ($this->connection->getShardDefinition('filecache')) {
49
+            foreach ($this->previewService->getAvailableFileIds() as $availableFileIdGroup) {
50
+                $fileIds = $this->findMissingSources($availableFileIdGroup['storageId'], $availableFileIdGroup['fileIds']);
51
+                foreach ($fileIds as $fileId) {
52
+                    yield $fileId;
53
+                }
54
+            }
55
+            return;
56
+        }
57 57
 
58
-		/*
58
+        /*
59 59
 		 * Deleting a file will not delete related previews right away.
60 60
 		 *
61 61
 		 * A delete request is usually an HTTP request.
@@ -70,38 +70,38 @@  discard block
 block discarded – undo
70 70
 		 *
71 71
 		 * If the related file is deleted, b.fileid will be null and the preview folder can be deleted.
72 72
 		 */
73
-		$qb = $this->connection->getQueryBuilder();
74
-		$qb->select('p.file_id')
75
-			->from('previews', 'p')
76
-			->leftJoin('p', 'filecache', 'f', $qb->expr()->eq(
77
-				'p.file_id', 'f.fileid'
78
-			))
79
-			->where($qb->expr()->isNull('f.fileid'));
73
+        $qb = $this->connection->getQueryBuilder();
74
+        $qb->select('p.file_id')
75
+            ->from('previews', 'p')
76
+            ->leftJoin('p', 'filecache', 'f', $qb->expr()->eq(
77
+                'p.file_id', 'f.fileid'
78
+            ))
79
+            ->where($qb->expr()->isNull('f.fileid'));
80 80
 
81
-		if (!$this->isCLI) {
82
-			$qb->setMaxResults(10);
83
-		}
81
+        if (!$this->isCLI) {
82
+            $qb->setMaxResults(10);
83
+        }
84 84
 
85
-		$cursor = $qb->executeQuery();
86
-		while ($row = $cursor->fetch()) {
87
-			yield (int)$row['file_id'];
88
-		}
89
-		$cursor->closeCursor();
90
-	}
85
+        $cursor = $qb->executeQuery();
86
+        while ($row = $cursor->fetch()) {
87
+            yield (int)$row['file_id'];
88
+        }
89
+        $cursor->closeCursor();
90
+    }
91 91
 
92
-	/**
93
-	 * @param FileId[] $ids
94
-	 * @return FileId[]
95
-	 */
96
-	private function findMissingSources(int $storage, array $ids): array {
97
-		$qb = $this->connection->getQueryBuilder();
98
-		$qb->select('fileid')
99
-			->from('filecache')
100
-			->where($qb->expr()->andX(
101
-				$qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)),
102
-				$qb->expr()->eq('storage', $qb->createNamedParameter($storage, IQueryBuilder::PARAM_INT)),
103
-			));
104
-		$found = $qb->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
105
-		return array_diff($ids, $found);
106
-	}
92
+    /**
93
+     * @param FileId[] $ids
94
+     * @return FileId[]
95
+     */
96
+    private function findMissingSources(int $storage, array $ids): array {
97
+        $qb = $this->connection->getQueryBuilder();
98
+        $qb->select('fileid')
99
+            ->from('filecache')
100
+            ->where($qb->expr()->andX(
101
+                $qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)),
102
+                $qb->expr()->eq('storage', $qb->createNamedParameter($storage, IQueryBuilder::PARAM_INT)),
103
+            ));
104
+        $found = $qb->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
105
+        return array_diff($ids, $found);
106
+    }
107 107
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -84,7 +84,7 @@
 block discarded – undo
84 84
 
85 85
 		$cursor = $qb->executeQuery();
86 86
 		while ($row = $cursor->fetch()) {
87
-			yield (int)$row['file_id'];
87
+			yield (int) $row['file_id'];
88 88
 		}
89 89
 		$cursor->closeCursor();
90 90
 	}
Please login to merge, or discard this patch.
lib/private/Preview/GeneratorHelper.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -19,47 +19,47 @@
 block discarded – undo
19 19
  * Very small wrapper class to make the generator fully unit testable
20 20
  */
21 21
 class GeneratorHelper {
22
-	/** @var IRootFolder */
23
-	private $rootFolder;
22
+    /** @var IRootFolder */
23
+    private $rootFolder;
24 24
 
25
-	/** @var IConfig */
26
-	private $config;
25
+    /** @var IConfig */
26
+    private $config;
27 27
 
28
-	public function __construct(IRootFolder $rootFolder, IConfig $config) {
29
-		$this->rootFolder = $rootFolder;
30
-		$this->config = $config;
31
-	}
28
+    public function __construct(IRootFolder $rootFolder, IConfig $config) {
29
+        $this->rootFolder = $rootFolder;
30
+        $this->config = $config;
31
+    }
32 32
 
33
-	/**
34
-	 * @param IProviderV2 $provider
35
-	 * @param File $file
36
-	 * @param int $maxWidth
37
-	 * @param int $maxHeight
38
-	 *
39
-	 * @return bool|IImage
40
-	 */
41
-	public function getThumbnail(IProviderV2 $provider, File $file, $maxWidth, $maxHeight, bool $crop = false) {
42
-		if ($provider instanceof Imaginary) {
43
-			return $provider->getCroppedThumbnail($file, $maxWidth, $maxHeight, $crop) ?? false;
44
-		}
45
-		return $provider->getThumbnail($file, $maxWidth, $maxHeight) ?? false;
46
-	}
33
+    /**
34
+     * @param IProviderV2 $provider
35
+     * @param File $file
36
+     * @param int $maxWidth
37
+     * @param int $maxHeight
38
+     *
39
+     * @return bool|IImage
40
+     */
41
+    public function getThumbnail(IProviderV2 $provider, File $file, $maxWidth, $maxHeight, bool $crop = false) {
42
+        if ($provider instanceof Imaginary) {
43
+            return $provider->getCroppedThumbnail($file, $maxWidth, $maxHeight, $crop) ?? false;
44
+        }
45
+        return $provider->getThumbnail($file, $maxWidth, $maxHeight) ?? false;
46
+    }
47 47
 
48
-	public function getImage(ISimpleFile $maxPreview): IImage {
49
-		$image = new OCPImage();
50
-		$image->loadFromData($maxPreview->getContent());
51
-		return $image;
52
-	}
48
+    public function getImage(ISimpleFile $maxPreview): IImage {
49
+        $image = new OCPImage();
50
+        $image->loadFromData($maxPreview->getContent());
51
+        return $image;
52
+    }
53 53
 
54
-	/**
55
-	 * @param callable $providerClosure
56
-	 * @return IProviderV2
57
-	 */
58
-	public function getProvider($providerClosure) {
59
-		$provider = $providerClosure();
60
-		if ($provider instanceof IProvider) {
61
-			$provider = new ProviderV1Adapter($provider);
62
-		}
63
-		return $provider;
64
-	}
54
+    /**
55
+     * @param callable $providerClosure
56
+     * @return IProviderV2
57
+     */
58
+    public function getProvider($providerClosure) {
59
+        $provider = $providerClosure();
60
+        if ($provider instanceof IProvider) {
61
+            $provider = new ProviderV1Adapter($provider);
62
+        }
63
+        return $provider;
64
+    }
65 65
 }
Please login to merge, or discard this patch.
lib/private/Preview/Generator.php 2 patches
Indentation   +512 added lines, -512 removed lines patch added patch discarded remove patch
@@ -27,530 +27,530 @@
 block discarded – undo
27 27
 use Psr\Log\LoggerInterface;
28 28
 
29 29
 class Generator {
30
-	public const SEMAPHORE_ID_ALL = 0x0a11;
31
-	public const SEMAPHORE_ID_NEW = 0x07ea;
32
-
33
-	public function __construct(
34
-		private IConfig $config,
35
-		private IPreview $previewManager,
36
-		private GeneratorHelper $helper,
37
-		private IEventDispatcher $eventDispatcher,
38
-		private LoggerInterface $logger,
39
-		private PreviewMapper $previewMapper,
40
-		private StorageFactory $storageFactory,
41
-	) {
42
-	}
43
-
44
-	/**
45
-	 * Returns a preview of a file
46
-	 *
47
-	 * The cache is searched first and if nothing usable was found then a preview is
48
-	 * generated by one of the providers
49
-	 *
50
-	 * @return ISimpleFile
51
-	 * @throws NotFoundException
52
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
53
-	 */
54
-	public function getPreview(
55
-		File $file,
56
-		int $width = -1,
57
-		int $height = -1,
58
-		bool $crop = false,
59
-		string $mode = IPreview::MODE_FILL,
60
-		?string $mimeType = null,
61
-		bool $cacheResult = true,
62
-	): ISimpleFile {
63
-		$specification = [
64
-			'width' => $width,
65
-			'height' => $height,
66
-			'crop' => $crop,
67
-			'mode' => $mode,
68
-		];
69
-
70
-		$this->eventDispatcher->dispatchTyped(new BeforePreviewFetchedEvent(
71
-			$file,
72
-			$width,
73
-			$height,
74
-			$crop,
75
-			$mode,
76
-			$mimeType,
77
-		));
78
-
79
-		$this->logger->debug('Requesting preview for {path} with width={width}, height={height}, crop={crop}, mode={mode}, mimeType={mimeType}', [
80
-			'path' => $file->getPath(),
81
-			'width' => $width,
82
-			'height' => $height,
83
-			'crop' => $crop,
84
-			'mode' => $mode,
85
-			'mimeType' => $mimeType,
86
-		]);
87
-
88
-
89
-		// since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
90
-		return $this->generatePreviews($file, [$specification], $mimeType, $cacheResult);
91
-	}
92
-
93
-	/**
94
-	 * Generates previews of a file
95
-	 *
96
-	 * @throws NotFoundException
97
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
98
-	 */
99
-	public function generatePreviews(File $file, array $specifications, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile {
100
-		//Make sure that we can read the file
101
-		if (!$file->isReadable()) {
102
-			$this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]);
103
-			throw new NotFoundException('Cannot read file');
104
-		}
105
-
106
-		if ($mimeType === null) {
107
-			$mimeType = $file->getMimeType();
108
-		}
109
-
110
-		[$file->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$file->getId()]);
111
-
112
-		$previewVersion = null;
113
-		if ($file instanceof IVersionedPreviewFile) {
114
-			$previewVersion = $file->getPreviewVersion();
115
-		}
116
-
117
-		// Get the max preview and infer the max preview sizes from that
118
-		$maxPreview = $this->getMaxPreview($previews, $file, $mimeType, $previewVersion);
119
-		$maxPreviewImage = null; // only load the image when we need it
120
-		if ($maxPreview->getSize() === 0) {
121
-			$this->storageFactory->deletePreview($maxPreview);
122
-			$this->previewMapper->delete($maxPreview);
123
-			$this->logger->error('Max preview generated for file {path} has size 0, deleting and throwing exception.', ['path' => $file->getPath()]);
124
-			throw new NotFoundException('Max preview size 0, invalid!');
125
-		}
126
-
127
-		$maxWidth = $maxPreview->getWidth();
128
-		$maxHeight = $maxPreview->getHeight();
129
-
130
-		if ($maxWidth <= 0 || $maxHeight <= 0) {
131
-			throw new NotFoundException('The maximum preview sizes are zero or less pixels');
132
-		}
133
-
134
-		$previewFile = null;
135
-		foreach ($specifications as $specification) {
136
-			$width = $specification['width'] ?? -1;
137
-			$height = $specification['height'] ?? -1;
138
-			$crop = $specification['crop'] ?? false;
139
-			$mode = $specification['mode'] ?? IPreview::MODE_FILL;
140
-
141
-			// If both width and height are -1 we just want the max preview
142
-			if ($width === -1 && $height === -1) {
143
-				$width = $maxWidth;
144
-				$height = $maxHeight;
145
-			}
146
-
147
-			// Calculate the preview size
148
-			[$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
149
-
150
-			// No need to generate a preview that is just the max preview
151
-			if ($width === $maxWidth && $height === $maxHeight) {
152
-				// ensure correct return value if this was the last one
153
-				$previewFile = new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper);
154
-				continue;
155
-			}
156
-
157
-			// Try to get a cached preview. Else generate (and store) one
158
-			try {
159
-				$preview = array_find($previews, fn (Preview $preview): bool => $preview->getWidth() === $width
160
-					&& $preview->getHeight() === $height && $preview->getMimetype() === $maxPreview->getMimetype()
161
-					&& $preview->getVersion() === $previewVersion && $preview->isCropped() === $crop);
162
-
163
-				if ($preview) {
164
-					$previewFile = new PreviewFile($preview, $this->storageFactory, $this->previewMapper);
165
-				} else {
166
-					if (!$this->previewManager->isMimeSupported($mimeType)) {
167
-						throw new NotFoundException();
168
-					}
169
-
170
-					if ($maxPreviewImage === null) {
171
-						$maxPreviewImage = $this->helper->getImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper));
172
-					}
173
-
174
-					$this->logger->debug('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]);
175
-					$previewFile = $this->generatePreview($file, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion, $cacheResult);
176
-				}
177
-			} catch (\InvalidArgumentException $e) {
178
-				throw new NotFoundException('', 0, $e);
179
-			}
180
-
181
-			if ($previewFile->getSize() === 0) {
182
-				$previewFile->delete();
183
-				throw new NotFoundException('Cached preview size 0, invalid!');
184
-			}
185
-		}
186
-		assert($previewFile !== null);
187
-
188
-		// Free memory being used by the embedded image resource.  Without this the image is kept in memory indefinitely.
189
-		// Garbage Collection does NOT free this memory.  We have to do it ourselves.
190
-		if ($maxPreviewImage instanceof \OCP\Image) {
191
-			$maxPreviewImage->destroy();
192
-		}
193
-
194
-		return $previewFile;
195
-	}
196
-
197
-	/**
198
-	 * Acquire a semaphore of the specified id and concurrency, blocking if necessary.
199
-	 * Return an identifier of the semaphore on success, which can be used to release it via
200
-	 * {@see Generator::unguardWithSemaphore()}.
201
-	 *
202
-	 * @param int $semId
203
-	 * @param int $concurrency
204
-	 * @return false|\SysvSemaphore the semaphore on success or false on failure
205
-	 */
206
-	public static function guardWithSemaphore(int $semId, int $concurrency) {
207
-		if (!extension_loaded('sysvsem')) {
208
-			return false;
209
-		}
210
-		$sem = sem_get($semId, $concurrency);
211
-		if ($sem === false) {
212
-			return false;
213
-		}
214
-		if (!sem_acquire($sem)) {
215
-			return false;
216
-		}
217
-		return $sem;
218
-	}
219
-
220
-	/**
221
-	 * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
222
-	 *
223
-	 * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
224
-	 * @return bool
225
-	 */
226
-	public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
227
-		if ($semId === false || !($semId instanceof \SysvSemaphore)) {
228
-			return false;
229
-		}
230
-		return sem_release($semId);
231
-	}
232
-
233
-	/**
234
-	 * Get the number of concurrent threads supported by the host.
235
-	 *
236
-	 * @return int number of concurrent threads, or 0 if it cannot be determined
237
-	 */
238
-	public static function getHardwareConcurrency(): int {
239
-		static $width;
240
-
241
-		if (!isset($width)) {
242
-			if (function_exists('ini_get')) {
243
-				$openBasedir = ini_get('open_basedir');
244
-				if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
245
-					$width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
246
-				} else {
247
-					$width = 0;
248
-				}
249
-			} else {
250
-				$width = 0;
251
-			}
252
-		}
253
-		return $width;
254
-	}
255
-
256
-	/**
257
-	 * Get number of concurrent preview generations from system config
258
-	 *
259
-	 * Two config entries, `preview_concurrency_new` and `preview_concurrency_all`,
260
-	 * are available. If not set, the default values are determined with the hardware concurrency
261
-	 * of the host. In case the hardware concurrency cannot be determined, or the user sets an
262
-	 * invalid value, fallback values are:
263
-	 * For new images whose previews do not exist and need to be generated, 4;
264
-	 * For all preview generation requests, 8.
265
-	 * Value of `preview_concurrency_all` should be greater than or equal to that of
266
-	 * `preview_concurrency_new`, otherwise, the latter is returned.
267
-	 *
268
-	 * @param string $type either `preview_concurrency_new` or `preview_concurrency_all`
269
-	 * @return int number of concurrent preview generations, or -1 if $type is invalid
270
-	 */
271
-	public function getNumConcurrentPreviews(string $type): int {
272
-		static $cached = [];
273
-		if (array_key_exists($type, $cached)) {
274
-			return $cached[$type];
275
-		}
276
-
277
-		$hardwareConcurrency = self::getHardwareConcurrency();
278
-		switch ($type) {
279
-			case 'preview_concurrency_all':
280
-				$fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency * 2 : 8;
281
-				$concurrency_all = $this->config->getSystemValueInt($type, $fallback);
282
-				$concurrency_new = $this->getNumConcurrentPreviews('preview_concurrency_new');
283
-				$cached[$type] = max($concurrency_all, $concurrency_new);
284
-				break;
285
-			case 'preview_concurrency_new':
286
-				$fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency : 4;
287
-				$cached[$type] = $this->config->getSystemValueInt($type, $fallback);
288
-				break;
289
-			default:
290
-				return -1;
291
-		}
292
-		return $cached[$type];
293
-	}
294
-
295
-	/**
296
-	 * @param Preview[] $previews
297
-	 * @throws NotFoundException
298
-	 */
299
-	private function getMaxPreview(array $previews, File $file, string $mimeType, ?string $version): Preview {
300
-		// We don't know the max preview size, so we can't use getCachedPreview.
301
-		// It might have been generated with a higher resolution than the current value.
302
-		foreach ($previews as $preview) {
303
-			if ($preview->isMax() && ($version === $preview->getVersion())) {
304
-				return $preview;
305
-			}
306
-		}
307
-
308
-		$maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
309
-		$maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
310
-
311
-		return $this->generateProviderPreview($file, $maxWidth, $maxHeight, false, true, $mimeType, $version);
312
-	}
313
-
314
-	private function generateProviderPreview(File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, ?string $version): Preview {
315
-		$previewProviders = $this->previewManager->getProviders();
316
-		foreach ($previewProviders as $supportedMimeType => $providers) {
317
-			// Filter out providers that does not support this mime
318
-			if (!preg_match($supportedMimeType, $mimeType)) {
319
-				continue;
320
-			}
321
-
322
-			foreach ($providers as $providerClosure) {
323
-
324
-				$provider = $this->helper->getProvider($providerClosure);
325
-				if (!($provider instanceof IProviderV2)) {
326
-					continue;
327
-				}
328
-
329
-				if (!$provider->isAvailable($file)) {
330
-					continue;
331
-				}
332
-
333
-				$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
334
-				$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
335
-				try {
336
-					$this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [
337
-						'mimeType' => $mimeType,
338
-						'width' => $width,
339
-						'height' => $height,
340
-					]);
341
-					$preview = $this->helper->getThumbnail($provider, $file, $width, $height);
342
-				} finally {
343
-					self::unguardWithSemaphore($sem);
344
-				}
345
-
346
-				if (!($preview instanceof IImage)) {
347
-					continue;
348
-				}
349
-
350
-				try {
351
-					$previewEntry = new Preview();
352
-					$previewEntry->setFileId($file->getId());
353
-					$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
354
-					$previewEntry->setSourceMimeType($file->getMimeType());
355
-					$previewEntry->setWidth($preview->width());
356
-					$previewEntry->setHeight($preview->height());
357
-					$previewEntry->setVersion($version);
358
-					$previewEntry->setMax($max);
359
-					$previewEntry->setCropped($crop);
360
-					$previewEntry->setEncrypted(false);
361
-					$previewEntry->setMimetype($preview->dataMimeType());
362
-					$previewEntry->setEtag($file->getEtag());
363
-					$previewEntry->setMtime((new \DateTime())->getTimestamp());
364
-					$previewEntry->setSize(0);
365
-					return $this->savePreview($previewEntry, $preview);
366
-				} catch (NotPermittedException) {
367
-					throw new NotFoundException();
368
-				}
369
-			}
370
-		}
371
-
372
-		throw new NotFoundException('No provider successfully handled the preview generation');
373
-	}
374
-
375
-	/**
376
-	 * @psalm-param IPreview::MODE_* $mode
377
-	 * @return int[]
378
-	 */
379
-	private function calculateSize(int $width, int $height, bool $crop, string $mode, int $maxWidth, int $maxHeight): array {
380
-		/*
30
+    public const SEMAPHORE_ID_ALL = 0x0a11;
31
+    public const SEMAPHORE_ID_NEW = 0x07ea;
32
+
33
+    public function __construct(
34
+        private IConfig $config,
35
+        private IPreview $previewManager,
36
+        private GeneratorHelper $helper,
37
+        private IEventDispatcher $eventDispatcher,
38
+        private LoggerInterface $logger,
39
+        private PreviewMapper $previewMapper,
40
+        private StorageFactory $storageFactory,
41
+    ) {
42
+    }
43
+
44
+    /**
45
+     * Returns a preview of a file
46
+     *
47
+     * The cache is searched first and if nothing usable was found then a preview is
48
+     * generated by one of the providers
49
+     *
50
+     * @return ISimpleFile
51
+     * @throws NotFoundException
52
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
53
+     */
54
+    public function getPreview(
55
+        File $file,
56
+        int $width = -1,
57
+        int $height = -1,
58
+        bool $crop = false,
59
+        string $mode = IPreview::MODE_FILL,
60
+        ?string $mimeType = null,
61
+        bool $cacheResult = true,
62
+    ): ISimpleFile {
63
+        $specification = [
64
+            'width' => $width,
65
+            'height' => $height,
66
+            'crop' => $crop,
67
+            'mode' => $mode,
68
+        ];
69
+
70
+        $this->eventDispatcher->dispatchTyped(new BeforePreviewFetchedEvent(
71
+            $file,
72
+            $width,
73
+            $height,
74
+            $crop,
75
+            $mode,
76
+            $mimeType,
77
+        ));
78
+
79
+        $this->logger->debug('Requesting preview for {path} with width={width}, height={height}, crop={crop}, mode={mode}, mimeType={mimeType}', [
80
+            'path' => $file->getPath(),
81
+            'width' => $width,
82
+            'height' => $height,
83
+            'crop' => $crop,
84
+            'mode' => $mode,
85
+            'mimeType' => $mimeType,
86
+        ]);
87
+
88
+
89
+        // since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
90
+        return $this->generatePreviews($file, [$specification], $mimeType, $cacheResult);
91
+    }
92
+
93
+    /**
94
+     * Generates previews of a file
95
+     *
96
+     * @throws NotFoundException
97
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
98
+     */
99
+    public function generatePreviews(File $file, array $specifications, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile {
100
+        //Make sure that we can read the file
101
+        if (!$file->isReadable()) {
102
+            $this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]);
103
+            throw new NotFoundException('Cannot read file');
104
+        }
105
+
106
+        if ($mimeType === null) {
107
+            $mimeType = $file->getMimeType();
108
+        }
109
+
110
+        [$file->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$file->getId()]);
111
+
112
+        $previewVersion = null;
113
+        if ($file instanceof IVersionedPreviewFile) {
114
+            $previewVersion = $file->getPreviewVersion();
115
+        }
116
+
117
+        // Get the max preview and infer the max preview sizes from that
118
+        $maxPreview = $this->getMaxPreview($previews, $file, $mimeType, $previewVersion);
119
+        $maxPreviewImage = null; // only load the image when we need it
120
+        if ($maxPreview->getSize() === 0) {
121
+            $this->storageFactory->deletePreview($maxPreview);
122
+            $this->previewMapper->delete($maxPreview);
123
+            $this->logger->error('Max preview generated for file {path} has size 0, deleting and throwing exception.', ['path' => $file->getPath()]);
124
+            throw new NotFoundException('Max preview size 0, invalid!');
125
+        }
126
+
127
+        $maxWidth = $maxPreview->getWidth();
128
+        $maxHeight = $maxPreview->getHeight();
129
+
130
+        if ($maxWidth <= 0 || $maxHeight <= 0) {
131
+            throw new NotFoundException('The maximum preview sizes are zero or less pixels');
132
+        }
133
+
134
+        $previewFile = null;
135
+        foreach ($specifications as $specification) {
136
+            $width = $specification['width'] ?? -1;
137
+            $height = $specification['height'] ?? -1;
138
+            $crop = $specification['crop'] ?? false;
139
+            $mode = $specification['mode'] ?? IPreview::MODE_FILL;
140
+
141
+            // If both width and height are -1 we just want the max preview
142
+            if ($width === -1 && $height === -1) {
143
+                $width = $maxWidth;
144
+                $height = $maxHeight;
145
+            }
146
+
147
+            // Calculate the preview size
148
+            [$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
149
+
150
+            // No need to generate a preview that is just the max preview
151
+            if ($width === $maxWidth && $height === $maxHeight) {
152
+                // ensure correct return value if this was the last one
153
+                $previewFile = new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper);
154
+                continue;
155
+            }
156
+
157
+            // Try to get a cached preview. Else generate (and store) one
158
+            try {
159
+                $preview = array_find($previews, fn (Preview $preview): bool => $preview->getWidth() === $width
160
+                    && $preview->getHeight() === $height && $preview->getMimetype() === $maxPreview->getMimetype()
161
+                    && $preview->getVersion() === $previewVersion && $preview->isCropped() === $crop);
162
+
163
+                if ($preview) {
164
+                    $previewFile = new PreviewFile($preview, $this->storageFactory, $this->previewMapper);
165
+                } else {
166
+                    if (!$this->previewManager->isMimeSupported($mimeType)) {
167
+                        throw new NotFoundException();
168
+                    }
169
+
170
+                    if ($maxPreviewImage === null) {
171
+                        $maxPreviewImage = $this->helper->getImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper));
172
+                    }
173
+
174
+                    $this->logger->debug('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]);
175
+                    $previewFile = $this->generatePreview($file, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion, $cacheResult);
176
+                }
177
+            } catch (\InvalidArgumentException $e) {
178
+                throw new NotFoundException('', 0, $e);
179
+            }
180
+
181
+            if ($previewFile->getSize() === 0) {
182
+                $previewFile->delete();
183
+                throw new NotFoundException('Cached preview size 0, invalid!');
184
+            }
185
+        }
186
+        assert($previewFile !== null);
187
+
188
+        // Free memory being used by the embedded image resource.  Without this the image is kept in memory indefinitely.
189
+        // Garbage Collection does NOT free this memory.  We have to do it ourselves.
190
+        if ($maxPreviewImage instanceof \OCP\Image) {
191
+            $maxPreviewImage->destroy();
192
+        }
193
+
194
+        return $previewFile;
195
+    }
196
+
197
+    /**
198
+     * Acquire a semaphore of the specified id and concurrency, blocking if necessary.
199
+     * Return an identifier of the semaphore on success, which can be used to release it via
200
+     * {@see Generator::unguardWithSemaphore()}.
201
+     *
202
+     * @param int $semId
203
+     * @param int $concurrency
204
+     * @return false|\SysvSemaphore the semaphore on success or false on failure
205
+     */
206
+    public static function guardWithSemaphore(int $semId, int $concurrency) {
207
+        if (!extension_loaded('sysvsem')) {
208
+            return false;
209
+        }
210
+        $sem = sem_get($semId, $concurrency);
211
+        if ($sem === false) {
212
+            return false;
213
+        }
214
+        if (!sem_acquire($sem)) {
215
+            return false;
216
+        }
217
+        return $sem;
218
+    }
219
+
220
+    /**
221
+     * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
222
+     *
223
+     * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
224
+     * @return bool
225
+     */
226
+    public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
227
+        if ($semId === false || !($semId instanceof \SysvSemaphore)) {
228
+            return false;
229
+        }
230
+        return sem_release($semId);
231
+    }
232
+
233
+    /**
234
+     * Get the number of concurrent threads supported by the host.
235
+     *
236
+     * @return int number of concurrent threads, or 0 if it cannot be determined
237
+     */
238
+    public static function getHardwareConcurrency(): int {
239
+        static $width;
240
+
241
+        if (!isset($width)) {
242
+            if (function_exists('ini_get')) {
243
+                $openBasedir = ini_get('open_basedir');
244
+                if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
245
+                    $width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
246
+                } else {
247
+                    $width = 0;
248
+                }
249
+            } else {
250
+                $width = 0;
251
+            }
252
+        }
253
+        return $width;
254
+    }
255
+
256
+    /**
257
+     * Get number of concurrent preview generations from system config
258
+     *
259
+     * Two config entries, `preview_concurrency_new` and `preview_concurrency_all`,
260
+     * are available. If not set, the default values are determined with the hardware concurrency
261
+     * of the host. In case the hardware concurrency cannot be determined, or the user sets an
262
+     * invalid value, fallback values are:
263
+     * For new images whose previews do not exist and need to be generated, 4;
264
+     * For all preview generation requests, 8.
265
+     * Value of `preview_concurrency_all` should be greater than or equal to that of
266
+     * `preview_concurrency_new`, otherwise, the latter is returned.
267
+     *
268
+     * @param string $type either `preview_concurrency_new` or `preview_concurrency_all`
269
+     * @return int number of concurrent preview generations, or -1 if $type is invalid
270
+     */
271
+    public function getNumConcurrentPreviews(string $type): int {
272
+        static $cached = [];
273
+        if (array_key_exists($type, $cached)) {
274
+            return $cached[$type];
275
+        }
276
+
277
+        $hardwareConcurrency = self::getHardwareConcurrency();
278
+        switch ($type) {
279
+            case 'preview_concurrency_all':
280
+                $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency * 2 : 8;
281
+                $concurrency_all = $this->config->getSystemValueInt($type, $fallback);
282
+                $concurrency_new = $this->getNumConcurrentPreviews('preview_concurrency_new');
283
+                $cached[$type] = max($concurrency_all, $concurrency_new);
284
+                break;
285
+            case 'preview_concurrency_new':
286
+                $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency : 4;
287
+                $cached[$type] = $this->config->getSystemValueInt($type, $fallback);
288
+                break;
289
+            default:
290
+                return -1;
291
+        }
292
+        return $cached[$type];
293
+    }
294
+
295
+    /**
296
+     * @param Preview[] $previews
297
+     * @throws NotFoundException
298
+     */
299
+    private function getMaxPreview(array $previews, File $file, string $mimeType, ?string $version): Preview {
300
+        // We don't know the max preview size, so we can't use getCachedPreview.
301
+        // It might have been generated with a higher resolution than the current value.
302
+        foreach ($previews as $preview) {
303
+            if ($preview->isMax() && ($version === $preview->getVersion())) {
304
+                return $preview;
305
+            }
306
+        }
307
+
308
+        $maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
309
+        $maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
310
+
311
+        return $this->generateProviderPreview($file, $maxWidth, $maxHeight, false, true, $mimeType, $version);
312
+    }
313
+
314
+    private function generateProviderPreview(File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, ?string $version): Preview {
315
+        $previewProviders = $this->previewManager->getProviders();
316
+        foreach ($previewProviders as $supportedMimeType => $providers) {
317
+            // Filter out providers that does not support this mime
318
+            if (!preg_match($supportedMimeType, $mimeType)) {
319
+                continue;
320
+            }
321
+
322
+            foreach ($providers as $providerClosure) {
323
+
324
+                $provider = $this->helper->getProvider($providerClosure);
325
+                if (!($provider instanceof IProviderV2)) {
326
+                    continue;
327
+                }
328
+
329
+                if (!$provider->isAvailable($file)) {
330
+                    continue;
331
+                }
332
+
333
+                $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
334
+                $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
335
+                try {
336
+                    $this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [
337
+                        'mimeType' => $mimeType,
338
+                        'width' => $width,
339
+                        'height' => $height,
340
+                    ]);
341
+                    $preview = $this->helper->getThumbnail($provider, $file, $width, $height);
342
+                } finally {
343
+                    self::unguardWithSemaphore($sem);
344
+                }
345
+
346
+                if (!($preview instanceof IImage)) {
347
+                    continue;
348
+                }
349
+
350
+                try {
351
+                    $previewEntry = new Preview();
352
+                    $previewEntry->setFileId($file->getId());
353
+                    $previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
354
+                    $previewEntry->setSourceMimeType($file->getMimeType());
355
+                    $previewEntry->setWidth($preview->width());
356
+                    $previewEntry->setHeight($preview->height());
357
+                    $previewEntry->setVersion($version);
358
+                    $previewEntry->setMax($max);
359
+                    $previewEntry->setCropped($crop);
360
+                    $previewEntry->setEncrypted(false);
361
+                    $previewEntry->setMimetype($preview->dataMimeType());
362
+                    $previewEntry->setEtag($file->getEtag());
363
+                    $previewEntry->setMtime((new \DateTime())->getTimestamp());
364
+                    $previewEntry->setSize(0);
365
+                    return $this->savePreview($previewEntry, $preview);
366
+                } catch (NotPermittedException) {
367
+                    throw new NotFoundException();
368
+                }
369
+            }
370
+        }
371
+
372
+        throw new NotFoundException('No provider successfully handled the preview generation');
373
+    }
374
+
375
+    /**
376
+     * @psalm-param IPreview::MODE_* $mode
377
+     * @return int[]
378
+     */
379
+    private function calculateSize(int $width, int $height, bool $crop, string $mode, int $maxWidth, int $maxHeight): array {
380
+        /*
381 381
 		 * If we are not cropping we have to make sure the requested image
382 382
 		 * respects the aspect ratio of the original.
383 383
 		 */
384
-		if (!$crop) {
385
-			$ratio = $maxHeight / $maxWidth;
384
+        if (!$crop) {
385
+            $ratio = $maxHeight / $maxWidth;
386 386
 
387
-			if ($width === -1) {
388
-				$width = $height / $ratio;
389
-			}
390
-			if ($height === -1) {
391
-				$height = $width * $ratio;
392
-			}
387
+            if ($width === -1) {
388
+                $width = $height / $ratio;
389
+            }
390
+            if ($height === -1) {
391
+                $height = $width * $ratio;
392
+            }
393 393
 
394
-			$ratioH = $height / $maxHeight;
395
-			$ratioW = $width / $maxWidth;
394
+            $ratioH = $height / $maxHeight;
395
+            $ratioW = $width / $maxWidth;
396 396
 
397
-			/*
397
+            /*
398 398
 			 * Fill means that the $height and $width are the max
399 399
 			 * Cover means min.
400 400
 			 */
401
-			if ($mode === IPreview::MODE_FILL) {
402
-				if ($ratioH > $ratioW) {
403
-					$height = $width * $ratio;
404
-				} else {
405
-					$width = $height / $ratio;
406
-				}
407
-			} elseif ($mode === IPreview::MODE_COVER) {
408
-				if ($ratioH > $ratioW) {
409
-					$width = $height / $ratio;
410
-				} else {
411
-					$height = $width * $ratio;
412
-				}
413
-			}
414
-		}
415
-
416
-		if ($height !== $maxHeight && $width !== $maxWidth) {
417
-			/*
401
+            if ($mode === IPreview::MODE_FILL) {
402
+                if ($ratioH > $ratioW) {
403
+                    $height = $width * $ratio;
404
+                } else {
405
+                    $width = $height / $ratio;
406
+                }
407
+            } elseif ($mode === IPreview::MODE_COVER) {
408
+                if ($ratioH > $ratioW) {
409
+                    $width = $height / $ratio;
410
+                } else {
411
+                    $height = $width * $ratio;
412
+                }
413
+            }
414
+        }
415
+
416
+        if ($height !== $maxHeight && $width !== $maxWidth) {
417
+            /*
418 418
 			 * Scale to the nearest power of four
419 419
 			 */
420
-			$pow4height = 4 ** ceil(log($height) / log(4));
421
-			$pow4width = 4 ** ceil(log($width) / log(4));
422
-
423
-			// Minimum size is 64
424
-			$pow4height = max($pow4height, 64);
425
-			$pow4width = max($pow4width, 64);
426
-
427
-			$ratioH = $height / $pow4height;
428
-			$ratioW = $width / $pow4width;
429
-
430
-			if ($ratioH < $ratioW) {
431
-				$width = $pow4width;
432
-				$height /= $ratioW;
433
-			} else {
434
-				$height = $pow4height;
435
-				$width /= $ratioH;
436
-			}
437
-		}
438
-
439
-		/*
420
+            $pow4height = 4 ** ceil(log($height) / log(4));
421
+            $pow4width = 4 ** ceil(log($width) / log(4));
422
+
423
+            // Minimum size is 64
424
+            $pow4height = max($pow4height, 64);
425
+            $pow4width = max($pow4width, 64);
426
+
427
+            $ratioH = $height / $pow4height;
428
+            $ratioW = $width / $pow4width;
429
+
430
+            if ($ratioH < $ratioW) {
431
+                $width = $pow4width;
432
+                $height /= $ratioW;
433
+            } else {
434
+                $height = $pow4height;
435
+                $width /= $ratioH;
436
+            }
437
+        }
438
+
439
+        /*
440 440
 		 * Make sure the requested height and width fall within the max
441 441
 		 * of the preview.
442 442
 		 */
443
-		if ($height > $maxHeight) {
444
-			$ratio = $height / $maxHeight;
445
-			$height = $maxHeight;
446
-			$width /= $ratio;
447
-		}
448
-		if ($width > $maxWidth) {
449
-			$ratio = $width / $maxWidth;
450
-			$width = $maxWidth;
451
-			$height /= $ratio;
452
-		}
453
-
454
-		return [(int)round($width), (int)round($height)];
455
-	}
456
-
457
-	/**
458
-	 * @throws NotFoundException
459
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
460
-	 */
461
-	private function generatePreview(
462
-		File $file,
463
-		IImage $maxPreview,
464
-		int $width,
465
-		int $height,
466
-		bool $crop,
467
-		int $maxWidth,
468
-		int $maxHeight,
469
-		?string $version,
470
-		bool $cacheResult,
471
-	): ISimpleFile {
472
-		$preview = $maxPreview;
473
-		if (!$preview->valid()) {
474
-			throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
475
-		}
476
-
477
-		$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
478
-		$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
479
-		try {
480
-			if ($crop) {
481
-				if ($height !== $preview->height() && $width !== $preview->width()) {
482
-					//Resize
483
-					$widthR = $preview->width() / $width;
484
-					$heightR = $preview->height() / $height;
485
-
486
-					if ($widthR > $heightR) {
487
-						$scaleH = $height;
488
-						$scaleW = $maxWidth / $heightR;
489
-					} else {
490
-						$scaleH = $maxHeight / $widthR;
491
-						$scaleW = $width;
492
-					}
493
-					$preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
494
-				}
495
-				$cropX = (int)floor(abs($width - $preview->width()) * 0.5);
496
-				$cropY = (int)floor(abs($height - $preview->height()) * 0.5);
497
-				$preview = $preview->cropCopy($cropX, $cropY, $width, $height);
498
-			} else {
499
-				$preview = $maxPreview->resizeCopy(max($width, $height));
500
-			}
501
-		} finally {
502
-			self::unguardWithSemaphore($sem);
503
-		}
504
-
505
-		$previewEntry = new Preview();
506
-		$previewEntry->setFileId($file->getId());
507
-		$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
508
-		$previewEntry->setWidth($width);
509
-		$previewEntry->setSourceMimeType($file->getMimeType());
510
-		$previewEntry->setHeight($height);
511
-		$previewEntry->setVersion($version);
512
-		$previewEntry->setMax(false);
513
-		$previewEntry->setCropped($crop);
514
-		$previewEntry->setEncrypted(false);
515
-		$previewEntry->setMimeType($preview->dataMimeType());
516
-		$previewEntry->setEtag($file->getEtag());
517
-		$previewEntry->setMtime((new \DateTime())->getTimestamp());
518
-		$previewEntry->setSize(0);
519
-		if ($cacheResult) {
520
-			$previewEntry = $this->savePreview($previewEntry, $preview);
521
-			return new PreviewFile($previewEntry, $this->storageFactory, $this->previewMapper);
522
-		} else {
523
-			return new InMemoryFile($previewEntry->getName(), $preview->data());
524
-		}
525
-	}
526
-
527
-	/**
528
-	 * @throws InvalidPathException
529
-	 * @throws NotFoundException
530
-	 * @throws NotPermittedException
531
-	 * @throws \OCP\DB\Exception
532
-	 */
533
-	public function savePreview(Preview $previewEntry, IImage $preview): Preview {
534
-		$previewEntry = $this->previewMapper->insert($previewEntry);
535
-
536
-		// we need to save to DB first
537
-		try {
538
-			if ($preview instanceof IStreamImage) {
539
-				$size = $this->storageFactory->writePreview($previewEntry, $preview->resource());
540
-			} else {
541
-				$stream = fopen('php://temp', 'w+');
542
-				fwrite($stream, $preview->data());
543
-				rewind($stream);
544
-				$size = $this->storageFactory->writePreview($previewEntry, $stream);
545
-			}
546
-			if (!$size) {
547
-				throw new \RuntimeException('Unable to write preview file');
548
-			}
549
-		} catch (\Exception $e) {
550
-			$this->previewMapper->delete($previewEntry);
551
-			throw $e;
552
-		}
553
-		$previewEntry->setSize($size);
554
-		return $this->previewMapper->update($previewEntry);
555
-	}
443
+        if ($height > $maxHeight) {
444
+            $ratio = $height / $maxHeight;
445
+            $height = $maxHeight;
446
+            $width /= $ratio;
447
+        }
448
+        if ($width > $maxWidth) {
449
+            $ratio = $width / $maxWidth;
450
+            $width = $maxWidth;
451
+            $height /= $ratio;
452
+        }
453
+
454
+        return [(int)round($width), (int)round($height)];
455
+    }
456
+
457
+    /**
458
+     * @throws NotFoundException
459
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
460
+     */
461
+    private function generatePreview(
462
+        File $file,
463
+        IImage $maxPreview,
464
+        int $width,
465
+        int $height,
466
+        bool $crop,
467
+        int $maxWidth,
468
+        int $maxHeight,
469
+        ?string $version,
470
+        bool $cacheResult,
471
+    ): ISimpleFile {
472
+        $preview = $maxPreview;
473
+        if (!$preview->valid()) {
474
+            throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
475
+        }
476
+
477
+        $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
478
+        $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
479
+        try {
480
+            if ($crop) {
481
+                if ($height !== $preview->height() && $width !== $preview->width()) {
482
+                    //Resize
483
+                    $widthR = $preview->width() / $width;
484
+                    $heightR = $preview->height() / $height;
485
+
486
+                    if ($widthR > $heightR) {
487
+                        $scaleH = $height;
488
+                        $scaleW = $maxWidth / $heightR;
489
+                    } else {
490
+                        $scaleH = $maxHeight / $widthR;
491
+                        $scaleW = $width;
492
+                    }
493
+                    $preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
494
+                }
495
+                $cropX = (int)floor(abs($width - $preview->width()) * 0.5);
496
+                $cropY = (int)floor(abs($height - $preview->height()) * 0.5);
497
+                $preview = $preview->cropCopy($cropX, $cropY, $width, $height);
498
+            } else {
499
+                $preview = $maxPreview->resizeCopy(max($width, $height));
500
+            }
501
+        } finally {
502
+            self::unguardWithSemaphore($sem);
503
+        }
504
+
505
+        $previewEntry = new Preview();
506
+        $previewEntry->setFileId($file->getId());
507
+        $previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
508
+        $previewEntry->setWidth($width);
509
+        $previewEntry->setSourceMimeType($file->getMimeType());
510
+        $previewEntry->setHeight($height);
511
+        $previewEntry->setVersion($version);
512
+        $previewEntry->setMax(false);
513
+        $previewEntry->setCropped($crop);
514
+        $previewEntry->setEncrypted(false);
515
+        $previewEntry->setMimeType($preview->dataMimeType());
516
+        $previewEntry->setEtag($file->getEtag());
517
+        $previewEntry->setMtime((new \DateTime())->getTimestamp());
518
+        $previewEntry->setSize(0);
519
+        if ($cacheResult) {
520
+            $previewEntry = $this->savePreview($previewEntry, $preview);
521
+            return new PreviewFile($previewEntry, $this->storageFactory, $this->previewMapper);
522
+        } else {
523
+            return new InMemoryFile($previewEntry->getName(), $preview->data());
524
+        }
525
+    }
526
+
527
+    /**
528
+     * @throws InvalidPathException
529
+     * @throws NotFoundException
530
+     * @throws NotPermittedException
531
+     * @throws \OCP\DB\Exception
532
+     */
533
+    public function savePreview(Preview $previewEntry, IImage $preview): Preview {
534
+        $previewEntry = $this->previewMapper->insert($previewEntry);
535
+
536
+        // we need to save to DB first
537
+        try {
538
+            if ($preview instanceof IStreamImage) {
539
+                $size = $this->storageFactory->writePreview($previewEntry, $preview->resource());
540
+            } else {
541
+                $stream = fopen('php://temp', 'w+');
542
+                fwrite($stream, $preview->data());
543
+                rewind($stream);
544
+                $size = $this->storageFactory->writePreview($previewEntry, $stream);
545
+            }
546
+            if (!$size) {
547
+                throw new \RuntimeException('Unable to write preview file');
548
+            }
549
+        } catch (\Exception $e) {
550
+            $this->previewMapper->delete($previewEntry);
551
+            throw $e;
552
+        }
553
+        $previewEntry->setSize($size);
554
+        return $this->previewMapper->update($previewEntry);
555
+    }
556 556
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -223,7 +223,7 @@  discard block
 block discarded – undo
223 223
 	 * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
224 224
 	 * @return bool
225 225
 	 */
226
-	public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
226
+	public static function unguardWithSemaphore(false | \SysvSemaphore $semId): bool {
227 227
 		if ($semId === false || !($semId instanceof \SysvSemaphore)) {
228 228
 			return false;
229 229
 		}
@@ -451,7 +451,7 @@  discard block
 block discarded – undo
451 451
 			$height /= $ratio;
452 452
 		}
453 453
 
454
-		return [(int)round($width), (int)round($height)];
454
+		return [(int) round($width), (int) round($height)];
455 455
 	}
456 456
 
457 457
 	/**
@@ -490,10 +490,10 @@  discard block
 block discarded – undo
490 490
 						$scaleH = $maxHeight / $widthR;
491 491
 						$scaleW = $width;
492 492
 					}
493
-					$preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
493
+					$preview = $preview->preciseResizeCopy((int) round($scaleW), (int) round($scaleH));
494 494
 				}
495
-				$cropX = (int)floor(abs($width - $preview->width()) * 0.5);
496
-				$cropY = (int)floor(abs($height - $preview->height()) * 0.5);
495
+				$cropX = (int) floor(abs($width - $preview->width()) * 0.5);
496
+				$cropY = (int) floor(abs($height - $preview->height()) * 0.5);
497 497
 				$preview = $preview->cropCopy($cropX, $cropY, $width, $height);
498 498
 			} else {
499 499
 				$preview = $maxPreview->resizeCopy(max($width, $height));
Please login to merge, or discard this patch.
lib/private/Preview/Storage/LocalPreviewStorage.php 2 patches
Indentation   +210 added lines, -210 removed lines patch added patch discarded remove patch
@@ -28,214 +28,214 @@
 block discarded – undo
28 28
 use RecursiveIteratorIterator;
29 29
 
30 30
 class LocalPreviewStorage implements IPreviewStorage {
31
-	private readonly string $rootFolder;
32
-	private readonly string $instanceId;
33
-
34
-	public function __construct(
35
-		private readonly IConfig $config,
36
-		private readonly PreviewMapper $previewMapper,
37
-		private readonly IAppConfig $appConfig,
38
-		private readonly IDBConnection $connection,
39
-		private readonly IMimeTypeDetector $mimeTypeDetector,
40
-		private readonly LoggerInterface $logger,
41
-	) {
42
-		$this->instanceId = $this->config->getSystemValueString('instanceid');
43
-		$this->rootFolder = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data');
44
-	}
45
-
46
-	#[Override]
47
-	public function writePreview(Preview $preview, mixed $stream): int {
48
-		$previewPath = $this->constructPath($preview);
49
-		$this->createParentFiles($previewPath);
50
-		return file_put_contents($previewPath, $stream);
51
-	}
52
-
53
-	#[Override]
54
-	public function readPreview(Preview $preview): mixed {
55
-		$previewPath = $this->constructPath($preview);
56
-		$resource = @fopen($previewPath, 'r');
57
-		if ($resource === false) {
58
-			throw new NotFoundException('Unable to open preview stream at ' . $previewPath);
59
-		}
60
-		return $resource;
61
-	}
62
-
63
-	#[Override]
64
-	public function deletePreview(Preview $preview): void {
65
-		$previewPath = $this->constructPath($preview);
66
-		if (!@unlink($previewPath) && is_file($previewPath)) {
67
-			throw new NotPermittedException('Unable to delete preview at ' . $previewPath);
68
-		}
69
-	}
70
-
71
-	public function getPreviewRootFolder(): string {
72
-		return $this->rootFolder . '/appdata_' . $this->instanceId . '/preview/';
73
-	}
74
-
75
-	private function constructPath(Preview $preview): string {
76
-		return $this->getPreviewRootFolder() . implode('/', str_split(substr(md5((string)$preview->getFileId()), 0, 7))) . '/' . $preview->getFileId() . '/' . $preview->getName();
77
-	}
78
-
79
-	private function createParentFiles(string $path): void {
80
-		$dirname = dirname($path);
81
-		@mkdir($dirname, recursive: true);
82
-		if (!is_dir($dirname)) {
83
-			throw new NotPermittedException("Unable to create directory '$dirname'");
84
-		}
85
-	}
86
-
87
-	#[Override]
88
-	public function migratePreview(Preview $preview, SimpleFile $file): void {
89
-		// legacy flat directory
90
-		$sourcePath = $this->getPreviewRootFolder() . $preview->getFileId() . '/' . $preview->getName();
91
-		if (!file_exists($sourcePath)) {
92
-			return;
93
-		}
94
-
95
-		$destinationPath = $this->constructPath($preview);
96
-		if (file_exists($destinationPath)) {
97
-			@unlink($sourcePath); // We already have a new preview, just delete the old one
98
-			return;
99
-		}
100
-
101
-		$this->createParentFiles($destinationPath);
102
-		$ok = rename($sourcePath, $destinationPath);
103
-		if (!$ok) {
104
-			throw new LogicException('Failed to move ' . $sourcePath . ' to ' . $destinationPath);
105
-		}
106
-	}
107
-
108
-	#[Override]
109
-	public function scan(): int {
110
-		$checkForFileCache = !$this->appConfig->getValueBool('core', 'previewMovedDone');
111
-
112
-		$scanner = new RecursiveDirectoryIterator($this->getPreviewRootFolder());
113
-		$previewsFound = 0;
114
-		foreach (new RecursiveIteratorIterator($scanner) as $file) {
115
-			if ($file->isFile()) {
116
-				$preview = Preview::fromPath((string)$file, $this->mimeTypeDetector);
117
-				if ($preview === false) {
118
-					$this->logger->error('Unable to parse preview information for ' . $file->getRealPath());
119
-					continue;
120
-				}
121
-				try {
122
-					$preview->setSize($file->getSize());
123
-					$preview->setMtime($file->getMtime());
124
-					$preview->setEncrypted(false);
125
-
126
-					$qb = $this->connection->getQueryBuilder();
127
-					$result = $qb->select('storage', 'etag', 'mimetype')
128
-						->from('filecache')
129
-						->where($qb->expr()->eq('fileid', $qb->createNamedParameter($preview->getFileId())))
130
-						->setMaxResults(1)
131
-						->runAcrossAllShards() // Unavoidable because we can't extract the storage_id from the preview name
132
-						->executeQuery()
133
-						->fetchAll();
134
-
135
-					if (empty($result)) {
136
-						// original file is deleted
137
-						@unlink($file->getRealPath());
138
-						continue;
139
-					}
140
-
141
-					if ($checkForFileCache) {
142
-						$relativePath = str_replace($this->rootFolder . '/', '', $file->getRealPath());
143
-						$rowAffected = $qb->delete('filecache')
144
-							->where($qb->expr()->eq('path_hash', $qb->createNamedParameter(md5($relativePath))))
145
-							->executeStatement();
146
-						if ($rowAffected > 0) {
147
-							$this->deleteParentsFromFileCache(dirname($relativePath));
148
-						}
149
-					}
150
-
151
-					$preview->setStorageId($result[0]['storage']);
152
-					$preview->setEtag($result[0]['etag']);
153
-					$preview->setSourceMimetype($result[0]['mimetype']);
154
-
155
-					// try to insert, if that fails the preview is already in the DB
156
-					$this->previewMapper->insert($preview);
157
-
158
-					// Move old flat preview to new format
159
-					$dirName = str_replace($this->getPreviewRootFolder(), '', $file->getPath());
160
-					if (preg_match('/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9]+/', $dirName) !== 1) {
161
-						$previewPath = $this->constructPath($preview);
162
-						$this->createParentFiles($previewPath);
163
-						$ok = rename($file->getRealPath(), $previewPath);
164
-						if (!$ok) {
165
-							throw new LogicException('Failed to move ' . $file->getRealPath() . ' to ' . $previewPath);
166
-						}
167
-					}
168
-				} catch (Exception $e) {
169
-					if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
170
-						throw $e;
171
-					}
172
-				}
173
-				$previewsFound++;
174
-			}
175
-		}
176
-
177
-		return $previewsFound;
178
-	}
179
-
180
-	private function deleteParentsFromFileCache(string $dirname): void {
181
-		$qb = $this->connection->getQueryBuilder();
182
-
183
-		$result = $qb->select('fileid', 'path', 'storage', 'parent')
184
-			->from('filecache')
185
-			->where($qb->expr()->eq('path_hash', $qb->createNamedParameter(md5($dirname))))
186
-			->setMaxResults(1)
187
-			->runAcrossAllShards()
188
-			->executeQuery()
189
-			->fetchAll();
190
-
191
-		if (empty($result)) {
192
-			return;
193
-		}
194
-
195
-		$this->connection->beginTransaction();
196
-
197
-		$parentId = $result[0]['parent'];
198
-		$fileId = $result[0]['fileid'];
199
-		$storage = $result[0]['storage'];
200
-
201
-		try {
202
-			while (true) {
203
-				$qb = $this->connection->getQueryBuilder();
204
-				$childs = $qb->select('fileid', 'path', 'storage')
205
-					->from('filecache')
206
-					->where($qb->expr()->eq('parent', $qb->createNamedParameter($fileId)))
207
-					->hintShardKey('storage', $storage)
208
-					->executeQuery()
209
-					->fetchAll();
210
-
211
-				if (!empty($childs)) {
212
-					break;
213
-				}
214
-
215
-				$qb = $this->connection->getQueryBuilder();
216
-				$qb->delete('filecache')
217
-					->where($qb->expr()->eq('fileid', $qb->createNamedParameter($fileId)))
218
-					->hintShardKey('storage', $result[0]['storage'])
219
-					->executeStatement();
220
-
221
-				$qb = $this->connection->getQueryBuilder();
222
-				$result = $qb->select('fileid', 'path', 'storage', 'parent')
223
-					->from('filecache')
224
-					->where($qb->expr()->eq('fileid', $qb->createNamedParameter($parentId)))
225
-					->setMaxResults(1)
226
-					->hintShardKey('storage', $storage)
227
-					->executeQuery()
228
-					->fetchAll();
229
-
230
-				if (empty($result)) {
231
-					break;
232
-				}
233
-
234
-				$fileId = $parentId;
235
-				$parentId = $result[0]['parent'];
236
-			}
237
-		} finally {
238
-			$this->connection->commit();
239
-		}
240
-	}
31
+    private readonly string $rootFolder;
32
+    private readonly string $instanceId;
33
+
34
+    public function __construct(
35
+        private readonly IConfig $config,
36
+        private readonly PreviewMapper $previewMapper,
37
+        private readonly IAppConfig $appConfig,
38
+        private readonly IDBConnection $connection,
39
+        private readonly IMimeTypeDetector $mimeTypeDetector,
40
+        private readonly LoggerInterface $logger,
41
+    ) {
42
+        $this->instanceId = $this->config->getSystemValueString('instanceid');
43
+        $this->rootFolder = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data');
44
+    }
45
+
46
+    #[Override]
47
+    public function writePreview(Preview $preview, mixed $stream): int {
48
+        $previewPath = $this->constructPath($preview);
49
+        $this->createParentFiles($previewPath);
50
+        return file_put_contents($previewPath, $stream);
51
+    }
52
+
53
+    #[Override]
54
+    public function readPreview(Preview $preview): mixed {
55
+        $previewPath = $this->constructPath($preview);
56
+        $resource = @fopen($previewPath, 'r');
57
+        if ($resource === false) {
58
+            throw new NotFoundException('Unable to open preview stream at ' . $previewPath);
59
+        }
60
+        return $resource;
61
+    }
62
+
63
+    #[Override]
64
+    public function deletePreview(Preview $preview): void {
65
+        $previewPath = $this->constructPath($preview);
66
+        if (!@unlink($previewPath) && is_file($previewPath)) {
67
+            throw new NotPermittedException('Unable to delete preview at ' . $previewPath);
68
+        }
69
+    }
70
+
71
+    public function getPreviewRootFolder(): string {
72
+        return $this->rootFolder . '/appdata_' . $this->instanceId . '/preview/';
73
+    }
74
+
75
+    private function constructPath(Preview $preview): string {
76
+        return $this->getPreviewRootFolder() . implode('/', str_split(substr(md5((string)$preview->getFileId()), 0, 7))) . '/' . $preview->getFileId() . '/' . $preview->getName();
77
+    }
78
+
79
+    private function createParentFiles(string $path): void {
80
+        $dirname = dirname($path);
81
+        @mkdir($dirname, recursive: true);
82
+        if (!is_dir($dirname)) {
83
+            throw new NotPermittedException("Unable to create directory '$dirname'");
84
+        }
85
+    }
86
+
87
+    #[Override]
88
+    public function migratePreview(Preview $preview, SimpleFile $file): void {
89
+        // legacy flat directory
90
+        $sourcePath = $this->getPreviewRootFolder() . $preview->getFileId() . '/' . $preview->getName();
91
+        if (!file_exists($sourcePath)) {
92
+            return;
93
+        }
94
+
95
+        $destinationPath = $this->constructPath($preview);
96
+        if (file_exists($destinationPath)) {
97
+            @unlink($sourcePath); // We already have a new preview, just delete the old one
98
+            return;
99
+        }
100
+
101
+        $this->createParentFiles($destinationPath);
102
+        $ok = rename($sourcePath, $destinationPath);
103
+        if (!$ok) {
104
+            throw new LogicException('Failed to move ' . $sourcePath . ' to ' . $destinationPath);
105
+        }
106
+    }
107
+
108
+    #[Override]
109
+    public function scan(): int {
110
+        $checkForFileCache = !$this->appConfig->getValueBool('core', 'previewMovedDone');
111
+
112
+        $scanner = new RecursiveDirectoryIterator($this->getPreviewRootFolder());
113
+        $previewsFound = 0;
114
+        foreach (new RecursiveIteratorIterator($scanner) as $file) {
115
+            if ($file->isFile()) {
116
+                $preview = Preview::fromPath((string)$file, $this->mimeTypeDetector);
117
+                if ($preview === false) {
118
+                    $this->logger->error('Unable to parse preview information for ' . $file->getRealPath());
119
+                    continue;
120
+                }
121
+                try {
122
+                    $preview->setSize($file->getSize());
123
+                    $preview->setMtime($file->getMtime());
124
+                    $preview->setEncrypted(false);
125
+
126
+                    $qb = $this->connection->getQueryBuilder();
127
+                    $result = $qb->select('storage', 'etag', 'mimetype')
128
+                        ->from('filecache')
129
+                        ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($preview->getFileId())))
130
+                        ->setMaxResults(1)
131
+                        ->runAcrossAllShards() // Unavoidable because we can't extract the storage_id from the preview name
132
+                        ->executeQuery()
133
+                        ->fetchAll();
134
+
135
+                    if (empty($result)) {
136
+                        // original file is deleted
137
+                        @unlink($file->getRealPath());
138
+                        continue;
139
+                    }
140
+
141
+                    if ($checkForFileCache) {
142
+                        $relativePath = str_replace($this->rootFolder . '/', '', $file->getRealPath());
143
+                        $rowAffected = $qb->delete('filecache')
144
+                            ->where($qb->expr()->eq('path_hash', $qb->createNamedParameter(md5($relativePath))))
145
+                            ->executeStatement();
146
+                        if ($rowAffected > 0) {
147
+                            $this->deleteParentsFromFileCache(dirname($relativePath));
148
+                        }
149
+                    }
150
+
151
+                    $preview->setStorageId($result[0]['storage']);
152
+                    $preview->setEtag($result[0]['etag']);
153
+                    $preview->setSourceMimetype($result[0]['mimetype']);
154
+
155
+                    // try to insert, if that fails the preview is already in the DB
156
+                    $this->previewMapper->insert($preview);
157
+
158
+                    // Move old flat preview to new format
159
+                    $dirName = str_replace($this->getPreviewRootFolder(), '', $file->getPath());
160
+                    if (preg_match('/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9a-e]\/[0-9]+/', $dirName) !== 1) {
161
+                        $previewPath = $this->constructPath($preview);
162
+                        $this->createParentFiles($previewPath);
163
+                        $ok = rename($file->getRealPath(), $previewPath);
164
+                        if (!$ok) {
165
+                            throw new LogicException('Failed to move ' . $file->getRealPath() . ' to ' . $previewPath);
166
+                        }
167
+                    }
168
+                } catch (Exception $e) {
169
+                    if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
170
+                        throw $e;
171
+                    }
172
+                }
173
+                $previewsFound++;
174
+            }
175
+        }
176
+
177
+        return $previewsFound;
178
+    }
179
+
180
+    private function deleteParentsFromFileCache(string $dirname): void {
181
+        $qb = $this->connection->getQueryBuilder();
182
+
183
+        $result = $qb->select('fileid', 'path', 'storage', 'parent')
184
+            ->from('filecache')
185
+            ->where($qb->expr()->eq('path_hash', $qb->createNamedParameter(md5($dirname))))
186
+            ->setMaxResults(1)
187
+            ->runAcrossAllShards()
188
+            ->executeQuery()
189
+            ->fetchAll();
190
+
191
+        if (empty($result)) {
192
+            return;
193
+        }
194
+
195
+        $this->connection->beginTransaction();
196
+
197
+        $parentId = $result[0]['parent'];
198
+        $fileId = $result[0]['fileid'];
199
+        $storage = $result[0]['storage'];
200
+
201
+        try {
202
+            while (true) {
203
+                $qb = $this->connection->getQueryBuilder();
204
+                $childs = $qb->select('fileid', 'path', 'storage')
205
+                    ->from('filecache')
206
+                    ->where($qb->expr()->eq('parent', $qb->createNamedParameter($fileId)))
207
+                    ->hintShardKey('storage', $storage)
208
+                    ->executeQuery()
209
+                    ->fetchAll();
210
+
211
+                if (!empty($childs)) {
212
+                    break;
213
+                }
214
+
215
+                $qb = $this->connection->getQueryBuilder();
216
+                $qb->delete('filecache')
217
+                    ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($fileId)))
218
+                    ->hintShardKey('storage', $result[0]['storage'])
219
+                    ->executeStatement();
220
+
221
+                $qb = $this->connection->getQueryBuilder();
222
+                $result = $qb->select('fileid', 'path', 'storage', 'parent')
223
+                    ->from('filecache')
224
+                    ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($parentId)))
225
+                    ->setMaxResults(1)
226
+                    ->hintShardKey('storage', $storage)
227
+                    ->executeQuery()
228
+                    ->fetchAll();
229
+
230
+                if (empty($result)) {
231
+                    break;
232
+                }
233
+
234
+                $fileId = $parentId;
235
+                $parentId = $result[0]['parent'];
236
+            }
237
+        } finally {
238
+            $this->connection->commit();
239
+        }
240
+    }
241 241
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -40,7 +40,7 @@  discard block
 block discarded – undo
40 40
 		private readonly LoggerInterface $logger,
41 41
 	) {
42 42
 		$this->instanceId = $this->config->getSystemValueString('instanceid');
43
-		$this->rootFolder = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data');
43
+		$this->rootFolder = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT.'/data');
44 44
 	}
45 45
 
46 46
 	#[Override]
@@ -55,7 +55,7 @@  discard block
 block discarded – undo
55 55
 		$previewPath = $this->constructPath($preview);
56 56
 		$resource = @fopen($previewPath, 'r');
57 57
 		if ($resource === false) {
58
-			throw new NotFoundException('Unable to open preview stream at ' . $previewPath);
58
+			throw new NotFoundException('Unable to open preview stream at '.$previewPath);
59 59
 		}
60 60
 		return $resource;
61 61
 	}
@@ -64,16 +64,16 @@  discard block
 block discarded – undo
64 64
 	public function deletePreview(Preview $preview): void {
65 65
 		$previewPath = $this->constructPath($preview);
66 66
 		if (!@unlink($previewPath) && is_file($previewPath)) {
67
-			throw new NotPermittedException('Unable to delete preview at ' . $previewPath);
67
+			throw new NotPermittedException('Unable to delete preview at '.$previewPath);
68 68
 		}
69 69
 	}
70 70
 
71 71
 	public function getPreviewRootFolder(): string {
72
-		return $this->rootFolder . '/appdata_' . $this->instanceId . '/preview/';
72
+		return $this->rootFolder.'/appdata_'.$this->instanceId.'/preview/';
73 73
 	}
74 74
 
75 75
 	private function constructPath(Preview $preview): string {
76
-		return $this->getPreviewRootFolder() . implode('/', str_split(substr(md5((string)$preview->getFileId()), 0, 7))) . '/' . $preview->getFileId() . '/' . $preview->getName();
76
+		return $this->getPreviewRootFolder().implode('/', str_split(substr(md5((string) $preview->getFileId()), 0, 7))).'/'.$preview->getFileId().'/'.$preview->getName();
77 77
 	}
78 78
 
79 79
 	private function createParentFiles(string $path): void {
@@ -87,7 +87,7 @@  discard block
 block discarded – undo
87 87
 	#[Override]
88 88
 	public function migratePreview(Preview $preview, SimpleFile $file): void {
89 89
 		// legacy flat directory
90
-		$sourcePath = $this->getPreviewRootFolder() . $preview->getFileId() . '/' . $preview->getName();
90
+		$sourcePath = $this->getPreviewRootFolder().$preview->getFileId().'/'.$preview->getName();
91 91
 		if (!file_exists($sourcePath)) {
92 92
 			return;
93 93
 		}
@@ -101,7 +101,7 @@  discard block
 block discarded – undo
101 101
 		$this->createParentFiles($destinationPath);
102 102
 		$ok = rename($sourcePath, $destinationPath);
103 103
 		if (!$ok) {
104
-			throw new LogicException('Failed to move ' . $sourcePath . ' to ' . $destinationPath);
104
+			throw new LogicException('Failed to move '.$sourcePath.' to '.$destinationPath);
105 105
 		}
106 106
 	}
107 107
 
@@ -113,9 +113,9 @@  discard block
 block discarded – undo
113 113
 		$previewsFound = 0;
114 114
 		foreach (new RecursiveIteratorIterator($scanner) as $file) {
115 115
 			if ($file->isFile()) {
116
-				$preview = Preview::fromPath((string)$file, $this->mimeTypeDetector);
116
+				$preview = Preview::fromPath((string) $file, $this->mimeTypeDetector);
117 117
 				if ($preview === false) {
118
-					$this->logger->error('Unable to parse preview information for ' . $file->getRealPath());
118
+					$this->logger->error('Unable to parse preview information for '.$file->getRealPath());
119 119
 					continue;
120 120
 				}
121 121
 				try {
@@ -139,7 +139,7 @@  discard block
 block discarded – undo
139 139
 					}
140 140
 
141 141
 					if ($checkForFileCache) {
142
-						$relativePath = str_replace($this->rootFolder . '/', '', $file->getRealPath());
142
+						$relativePath = str_replace($this->rootFolder.'/', '', $file->getRealPath());
143 143
 						$rowAffected = $qb->delete('filecache')
144 144
 							->where($qb->expr()->eq('path_hash', $qb->createNamedParameter(md5($relativePath))))
145 145
 							->executeStatement();
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
 						$this->createParentFiles($previewPath);
163 163
 						$ok = rename($file->getRealPath(), $previewPath);
164 164
 						if (!$ok) {
165
-							throw new LogicException('Failed to move ' . $file->getRealPath() . ' to ' . $previewPath);
165
+							throw new LogicException('Failed to move '.$file->getRealPath().' to '.$previewPath);
166 166
 						}
167 167
 					}
168 168
 				} catch (Exception $e) {
Please login to merge, or discard this patch.
lib/private/Preview/Storage/PreviewFile.php 2 patches
Indentation   +62 added lines, -62 removed lines patch added patch discarded remove patch
@@ -16,66 +16,66 @@
 block discarded – undo
16 16
 use Override;
17 17
 
18 18
 class PreviewFile implements ISimpleFile {
19
-	public function __construct(
20
-		private readonly Preview $preview,
21
-		private readonly IPreviewStorage $storage,
22
-		private readonly PreviewMapper $previewMapper,
23
-	) {
24
-	}
25
-
26
-	#[Override]
27
-	public function getName(): string {
28
-		return $this->preview->getName();
29
-	}
30
-
31
-	#[Override]
32
-	public function getSize(): int|float {
33
-		return $this->preview->getSize();
34
-	}
35
-
36
-	#[Override]
37
-	public function getETag(): string {
38
-		return $this->preview->getEtag();
39
-	}
40
-
41
-	#[Override]
42
-	public function getMTime(): int {
43
-		return $this->preview->getMtime();
44
-	}
45
-
46
-	#[Override]
47
-	public function getContent(): string {
48
-		$stream = $this->storage->readPreview($this->preview);
49
-		return stream_get_contents($stream);
50
-	}
51
-
52
-	#[Override]
53
-	public function putContent($data): void {
54
-	}
55
-
56
-	#[Override]
57
-	public function delete(): void {
58
-		$this->storage->deletePreview($this->preview);
59
-		$this->previewMapper->delete($this->preview);
60
-	}
61
-
62
-	#[Override]
63
-	public function getMimeType(): string {
64
-		return $this->preview->getMimetype();
65
-	}
66
-
67
-	#[Override]
68
-	public function getExtension(): string {
69
-		return $this->preview->getExtension();
70
-	}
71
-
72
-	#[Override]
73
-	public function read() {
74
-		return $this->storage->readPreview($this->preview);
75
-	}
76
-
77
-	#[Override]
78
-	public function write() {
79
-		return false;
80
-	}
19
+    public function __construct(
20
+        private readonly Preview $preview,
21
+        private readonly IPreviewStorage $storage,
22
+        private readonly PreviewMapper $previewMapper,
23
+    ) {
24
+    }
25
+
26
+    #[Override]
27
+    public function getName(): string {
28
+        return $this->preview->getName();
29
+    }
30
+
31
+    #[Override]
32
+    public function getSize(): int|float {
33
+        return $this->preview->getSize();
34
+    }
35
+
36
+    #[Override]
37
+    public function getETag(): string {
38
+        return $this->preview->getEtag();
39
+    }
40
+
41
+    #[Override]
42
+    public function getMTime(): int {
43
+        return $this->preview->getMtime();
44
+    }
45
+
46
+    #[Override]
47
+    public function getContent(): string {
48
+        $stream = $this->storage->readPreview($this->preview);
49
+        return stream_get_contents($stream);
50
+    }
51
+
52
+    #[Override]
53
+    public function putContent($data): void {
54
+    }
55
+
56
+    #[Override]
57
+    public function delete(): void {
58
+        $this->storage->deletePreview($this->preview);
59
+        $this->previewMapper->delete($this->preview);
60
+    }
61
+
62
+    #[Override]
63
+    public function getMimeType(): string {
64
+        return $this->preview->getMimetype();
65
+    }
66
+
67
+    #[Override]
68
+    public function getExtension(): string {
69
+        return $this->preview->getExtension();
70
+    }
71
+
72
+    #[Override]
73
+    public function read() {
74
+        return $this->storage->readPreview($this->preview);
75
+    }
76
+
77
+    #[Override]
78
+    public function write() {
79
+        return false;
80
+    }
81 81
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -29,7 +29,7 @@
 block discarded – undo
29 29
 	}
30 30
 
31 31
 	#[Override]
32
-	public function getSize(): int|float {
32
+	public function getSize(): int | float {
33 33
 		return $this->preview->getSize();
34 34
 	}
35 35
 
Please login to merge, or discard this patch.