summaryrefslogtreecommitdiff
path: root/classes/Cache_Local.php
blob: 8ac634a3ee7986719fb355bf9c431273065ede93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?php
class Cache_Local implements Cache_Adapter {
	private string $dir;

	public function remove(string $filename): bool {
		return unlink($this->get_full_path($filename));
	}

	public function get_mtime(string $filename) {
		return filemtime($this->get_full_path($filename));
	}

	public function set_dir(string $dir) : void {
		$this->dir = Config::get(Config::CACHE_DIR) . "/" . basename(clean($dir));

		$this->make_dir();
	}

	public function get_dir(): string {
		return $this->dir;
	}

	public function make_dir(): bool {
		if (!is_dir($this->dir)) {
			return mkdir($this->dir);
		}
		return false;
	}

	public function is_writable(?string $filename = null): bool {
		if ($filename) {
			if (file_exists($this->get_full_path($filename)))
				return is_writable($this->get_full_path($filename));
			else
				return is_writable($this->dir);
		} else {
			return is_writable($this->dir);
		}
	}

	public function exists(string $filename): bool {
		return file_exists($this->get_full_path($filename));
	}

	/**
	 * @return int|false -1 if the file doesn't exist, false if an error occurred, size in bytes otherwise
	 */
	public function get_size(string $filename) {
		if ($this->exists($filename))
			return filesize($this->get_full_path($filename));
		else
			return -1;
	}

	public function get_full_path(string $filename): string {
		return $this->dir . "/" . basename(clean($filename));
	}

	public function get(string $filename): ?string {
		if ($this->exists($filename))
			return file_get_contents($this->get_full_path($filename));
		else
			return null;
	}

	/**
	 * @param mixed $data
	 *
	 * @return int|false Bytes written or false if an error occurred.
	 */
	public function put(string $filename, $data) {
		return file_put_contents($this->get_full_path($filename), $data);
	}

	/**
	 * @return false|null|string false if detection failed, null if the file doesn't exist, string mime content type otherwise
	 */
	public function get_mime_type(string $filename) {
		if ($this->exists($filename))
			return mime_content_type($this->get_full_path($filename));
		else
			return null;
	}

	/**
	 * @return bool|int false if the file doesn't exist (or unreadable) or isn't audio/video, true if a plugin handled, otherwise int of bytes sent
	 */
	public function send(string $filename) {
		return $this->send_local_file($this->get_full_path($filename));
	}

	public function expire_all(): void {
		$dirs = array_filter(glob(Config::get(Config::CACHE_DIR) . "/*"), "is_dir");

		foreach ($dirs as $cache_dir) {
			$num_deleted = 0;

			if (is_writable($cache_dir) && !file_exists("$cache_dir/.no-auto-expiry")) {
				$files = glob("$cache_dir/*");

				if ($files) {
					foreach ($files as $file) {
						if (time() - filemtime($file) > 86400 * Config::get(Config::CACHE_MAX_DAYS)) {
							unlink($file);

							++$num_deleted;
						}
					}
				}

				Debug::log("Expired $cache_dir: removed $num_deleted files.");
			}
		}
	}

	/**
	 * this is essentially a wrapper for readfile() which allows plugins to hook
	 * output with httpd-specific "fast" implementation i.e. X-Sendfile or whatever else
	 *
	 * hook function should return true if request was handled (or at least attempted to)
	 *
	 * note that this can be called without user context so the plugin to handle this
	 * should be loaded systemwide in config.php
	 *
	 * @return bool|int false if the file doesn't exist (or unreadable) or isn't audio/video, true if a plugin handled, otherwise int of bytes sent
	 */
	private function send_local_file(string $filename) {
		if (file_exists($filename)) {

			if (is_writable($filename) && !$this->exists('.no-auto-expiry')) {
				touch($filename);
			}

			$tmppluginhost = new PluginHost();

			$tmppluginhost->load(Config::get(Config::PLUGINS), PluginHost::KIND_SYSTEM);
			//$tmppluginhost->load_data();

			if ($tmppluginhost->run_hooks_until(PluginHost::HOOK_SEND_LOCAL_FILE, true, $filename))
				return true;

			return readfile($filename);
		} else {
			return false;
		}
	}

}