1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Models\Wiki;
7
8use App\Exceptions\GitHubNotFoundException;
9use App\Libraries\OsuWiki;
10use Exception;
11use Illuminate\Contracts\Cache\LockTimeoutException;
12
13class Image implements WikiObject
14{
15 const CACHE_DURATION = 2 * 60 * 60;
16
17 public $path;
18
19 private $cache;
20
21 public function __construct($path)
22 {
23 $this->path = OsuWiki::cleanPath($path);
24 $this->cache = $this->fetchFromCache();
25 }
26
27 public function cacheKey()
28 {
29 return 'wiki:image:data:v2:'.$this->path;
30 }
31
32 public function get()
33 {
34 return $this->cache['data'] ?? null;
35 }
36
37 public function isVisible()
38 {
39 return $this->get() !== null;
40 }
41
42 public function needsSync()
43 {
44 return $this->cache === null
45 || ($this->cache['cached_at'] + static::CACHE_DURATION) < time();
46 }
47
48 public function sync($force = false)
49 {
50 if (!$force && !$this->needsSync()) {
51 return $this;
52 }
53
54 $cache = cache();
55 $cacheKey = $this->cacheKey();
56 $lock = $cache->lock($cacheKey.':lock', 300);
57
58 if (!$lock->get()) {
59 if ($this->cache !== null) {
60 return $this;
61 }
62
63 try {
64 $lock->block(10);
65 $lock->release();
66 $this->cache = $this->fetchFromCache();
67
68 return $this->sync($force);
69 } catch (LockTimeoutException $_e) {
70 // previous attempt is taking too long; try fetching the image anyway
71 }
72 }
73
74 try {
75 $content = OsuWiki::fetchContent('wiki/'.$this->path);
76 $type = image_type_to_mime_type(
77 read_image_properties_from_string($content)[2] ?? null
78 );
79
80 $data = compact('content', 'type');
81 } catch (GitHubNotFoundException $e) {
82 // do nothing and cache empty data
83 } catch (Exception $e) {
84 log_error($e);
85 $lock->release();
86
87 return $this;
88 }
89
90 try {
91 $this->cache = [
92 'data' => $data ?? null,
93 'cached_at' => time(),
94 ];
95 $cache->put($cacheKey, $this->cache);
96 } finally {
97 $lock->release();
98 }
99
100 return $this;
101 }
102
103 private function fetchFromCache(): ?array
104 {
105 return cache()->get($this->cacheKey());
106 }
107}