1<?php
2
3
4namespace ComboStrap;
5
6
7use DateTime;
8
9class LocalFileSystem implements FileSystem
10{
11
12    // same as the uri: ie local file os system
13    public const SCHEME = "file";
14
15    /**
16     * @var LocalFileSystem
17     */
18    private static $localFs;
19
20    public static function getOrCreate(): LocalFileSystem
21    {
22        if (self::$localFs === null) {
23            self::$localFs = new LocalFileSystem();
24        }
25        return self::$localFs;
26    }
27
28    function exists(Path $path): bool
29    {
30        return file_exists($path->toAbsolutePath()->toAbsoluteId());
31    }
32
33    /**
34     * @param $path
35     * @return string - textual content
36     * @throws ExceptionNotFound - if the file does not exist
37     */
38    public function getContent($path): string
39    {
40        /**
41         * Mime check
42         */
43        try {
44            $mime = FileSystems::getMime($path);
45            if (!$mime->isTextBased()) {
46                LogUtility::error("This mime content ($mime) is not text based (for the path $path). We can't return a text.");
47                return "";
48            }
49        } catch (ExceptionNotFound $e) {
50            LogUtility::error("The mime is unknown for the path ($path). Trying to returning the content as text.");
51        }
52        $content = @file_get_contents($path->toAbsolutePath()->toAbsoluteId());
53        if ($content === false) {
54            // file does not exists
55            throw new ExceptionNotFound("The file ($path) does not exists");
56        }
57        return $content;
58    }
59
60    /**
61     * @param LocalPath $path
62     * @return DateTime
63     * @throws ExceptionNotFound - if the file does not exist
64     */
65    public function getModifiedTime($path): DateTime
66    {
67        if (!self::exists($path)) {
68            throw new ExceptionNotFound("Local File System Modified Time: The file ($path) does not exist");
69        }
70        $timestamp = filemtime($path->toCanonicalAbsolutePath()->toAbsoluteId());
71        return Iso8601Date::createFromTimestamp($timestamp)->getDateTime();
72    }
73
74    /**
75     * @throws ExceptionNotFound
76     */
77    public function getCreationTime(Path $path)
78    {
79        if (!$this->exists($path)) {
80            throw new ExceptionNotFound("The path ($path) does not exists, no creation time");
81        }
82        $filePath = $path->toAbsolutePath()->toAbsoluteId();
83        $timestamp = filectime($filePath);
84        return Iso8601Date::createFromTimestamp($timestamp)->getDateTime();
85    }
86
87    /**
88     * @throws ExceptionFileSystem - if the action cannot be performed
89     */
90    public function delete(Path $path)
91    {
92        $absolutePath = $path->toAbsolutePath()->toAbsoluteId();
93        $success = unlink($absolutePath);
94        if(!$success){
95            throw new ExceptionFileSystem("Unable to delete the file ($absolutePath)");
96        }
97    }
98
99    /**
100     * @return false|int
101     * @var LocalPath $path
102     */
103    public function getSize(Path $path)
104    {
105        return filesize($path->toAbsolutePath()->toAbsoluteId());
106    }
107
108    /**
109     * @throws ExceptionCompile
110     */
111    public function createDirectory(Path $dirPath): Path
112    {
113        $result = mkdir($dirPath->toAbsolutePath()->toAbsoluteId(), $mode = 0770, $recursive = true);
114        if ($result === false) {
115            throw new ExceptionCompile("Unable to create the directory path ($dirPath)");
116        }
117        return $dirPath;
118    }
119
120    public function isDirectory(Path $path): bool
121    {
122        return is_dir($path->toAbsolutePath());
123    }
124
125    /**
126     * @param LocalPath $path
127     * @param string|null $type container / leaf (ie directory / file or namespace/page)
128     * @return LocalPath[]
129     */
130    public function getChildren(Path $path, string $type = null): array
131    {
132
133        /**
134         * Same as {@link scandir()}, they output
135         * the current and parent relative directory (ie `.` and `..`)
136         */
137        $directoryHandle = @opendir($path->toAbsolutePath());
138        if (!$directoryHandle) return [];
139        try {
140            $localChildren = [];
141            while (($fileName = readdir($directoryHandle)) !== false) {
142                if (in_array($fileName, [LocalPath::RELATIVE_CURRENT, LocalPath::RELATIVE_PARENT])) {
143                    continue;
144                }
145                $childPath = $path->resolve($fileName);
146                if ($type === null) {
147                    $localChildren[] = $childPath;
148                    continue;
149                }
150                /**
151                 * Filter is not null, filter
152                 */
153                switch ($type) {
154                    case FileSystems::CONTAINER:
155                        if (FileSystems::isDirectory($childPath)) {
156                            $localChildren[] = $childPath;
157                        }
158                        break;
159                    case FileSystems::LEAF:
160                        if (!FileSystems::isDirectory($childPath)) {
161                            $localChildren[] = $childPath;
162                        }
163                        break;
164                    default:
165                        LogUtility::internalError("The type of file ($type) is unknown. It should be `" . FileSystems::CONTAINER . "` or `" . FileSystems::LEAF . "`");
166                        $localChildren[] = $childPath;
167                }
168            }
169            /**
170             * With the default, the file '10_....' is before the file '01....'
171             */
172            sort($localChildren, SORT_NATURAL);
173            return $localChildren;
174        } finally {
175            closedir($directoryHandle);
176        }
177
178    }
179
180    /**
181     * @param LocalPath $path
182     * @param string $lastFullName
183     * @return Path
184     * @throws ExceptionNotFound
185     */
186    public function closest(Path $path, string $lastFullName): Path
187    {
188        if (FileSystems::isDirectory($path)) {
189            $closest = $path->resolve($lastFullName);
190            if (FileSystems::exists($closest)) {
191                return $closest;
192            }
193        }
194        $parent = $path;
195        while (true) {
196            try {
197                $parent = $parent->getParent();
198            } catch (ExceptionNotFound $e) {
199                break;
200            }
201            $closest = $parent->resolve($lastFullName);
202            if (FileSystems::exists($closest)) {
203                return $closest;
204            }
205        }
206        throw new ExceptionNotFound("No closest was found for the file name ($lastFullName) from the path ($path)");
207    }
208
209    /**
210     * @param LocalPath $path
211     * @return void
212     */
213    public function createRegularFile(Path $path)
214    {
215        touch($path->toAbsoluteId());
216    }
217
218    public function setContent(Path $path, string $content)
219    {
220
221        $file = $path->toAbsoluteId();
222        /**
223         * the {@link io_saveFile()} dokuwiki function
224         * expects the path to be with unix separator
225         * It fails to calculate the parent because it just don't use
226         * {@link dirname()} but search for the last /
227         * in {@link io_mkdir_p()}
228         */
229        $file = str_replace('\\','/',$file);
230        io_saveFile($file, $content, false);
231    }
232
233}
234