1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "io/file_manager.h"
17
18#include <algorithm>
19#include <cstddef>
20
21#include <base/containers/iterator.h>
22#include <base/containers/string.h>
23#include <base/containers/string_view.h>
24#include <base/containers/type_traits.h>
25#include <base/containers/unique_ptr.h>
26#include <base/containers/unordered_map.h>
27#include <base/containers/vector.h>
28#include <base/namespace.h>
29#include <base/util/uid.h>
30#include <core/io/intf_directory.h>
31#include <core/io/intf_file.h>
32#include <core/io/intf_file_manager.h>
33#include <core/io/intf_file_system.h>
34#include <core/log.h>
35#include <core/namespace.h>
36#include <core/plugin/intf_interface.h>
37
38#include "io/path_tools.h"
39#include "io/proxy_filesystem.h"
40#include "io/rofs_filesystem.h"
41#include "io/std_directory.h"
42
43CORE_BEGIN_NAMESPACE()
44using BASE_NS::make_unique;
45using BASE_NS::string;
46using BASE_NS::string_view;
47using BASE_NS::Uid;
48using BASE_NS::vector;
49
50string FileManager::FixPath(string_view pathIn) const
51{
52    string_view protocol;
53    string_view path;
54    if (ParseUri(pathIn, protocol, path)) {
55        // Try to identify relative "file" uris, and convert them to absolute.
56        if (protocol == "file") {
57            if (path.empty()) {
58                // so it's the base path then? (empty relative path)
59                return protocol + "://" + basePath_;
60            }
61#if _WIN32
62            // Handle win32 specific drive letters.
63            if (IsRelative(path)) {
64                // might still be absolute (if it has drive letter)
65                if ((path.size() > 1) && (path[1] == ':')) {
66                    // seems to start with drive letter so, it must be absolute?
67                    if (path.size() == 2) { // 2: path size
68                        // has only drive letter? consider it as root of drive then
69                        return protocol + ":///" + path + "/";
70                    }
71                    return protocol + ":///" + path;
72                }
73                // no drive letter so it's really relative.
74                return protocol + "://" + NormalizePath(basePath_ + path);
75            }
76            // Even if it's "absolute" it might still be missing the drive letter.
77            if ((path.size() < 3) || (path[2] != ':')) { // 3: path size limit; 2: the third letter
78                // seems to be missing the drive letter.
79                return protocol + "://" + NormalizePath(basePath_.substr(0, 3) + path); // 3: substring size
80            }
81            if (path.size() == 3) { // 3: path size
82                // has only drive letter? consider it as root of drive then
83                return protocol + "://" + path + "/";
84            }
85            return protocol + "://" + NormalizePath(path);
86#else
87            if (IsRelative(path)) {
88                // normalize it with current path..
89                return protocol + "://" + NormalizePath(basePath_ + path);
90            }
91            return protocol + "://" + NormalizePath(path);
92#endif
93        }
94    }
95    return string(pathIn);
96}
97
98FileManager::FileManager() : basePath_(GetCurrentDirectory()) {}
99
100const IInterface* FileManager::GetInterface(const Uid& uid) const
101{
102    return const_cast<FileManager*>(this)->GetInterface(uid);
103}
104
105IInterface* FileManager::GetInterface(const Uid& uid)
106{
107    if ((uid == IInterface::UID) || (uid == IFileManager::UID)) {
108        return this;
109    }
110    return nullptr;
111}
112
113void FileManager::Ref()
114{
115    refCount_++;
116}
117
118void FileManager::Unref()
119{
120    if (--refCount_ == 0) {
121        delete this;
122    }
123}
124
125IFile::Ptr FileManager::OpenFile(const string_view uriIn)
126{
127    string_view protocol;
128    string_view path;
129    auto uri = FixPath(uriIn);
130    if (ParseUri(uri, protocol, path)) {
131        IFilesystem* filesystem = GetFilesystem(protocol);
132        if (filesystem) {
133            return filesystem->OpenFile(path);
134        }
135        CORE_LOG_E("Failed to open file, no file system for uri: '%s'", string(uri).c_str());
136    } else {
137        CORE_LOG_E("Failed to open file, invalid uri: '%s'", string(uri).c_str());
138    }
139
140    return IFile::Ptr();
141}
142
143IFile::Ptr FileManager::CreateFile(const string_view uriIn)
144{
145    string_view protocol;
146    string_view path;
147    auto uri = FixPath(uriIn);
148    if (ParseUri(uri, protocol, path)) {
149        IFilesystem* filesystem = GetFilesystem(protocol);
150        if (filesystem) {
151            return filesystem->CreateFile(path);
152        }
153        CORE_LOG_E("Failed to create file, no file system for uri: '%s'", string(uri).c_str());
154    } else {
155        CORE_LOG_E("Failed to create file, invalid uri: '%s'", string(uri).c_str());
156    }
157
158    return IFile::Ptr();
159}
160
161bool FileManager::DeleteFile(const string_view uriIn)
162{
163    string_view protocol;
164    string_view path;
165    auto uri = FixPath(uriIn);
166    if (ParseUri(uri, protocol, path)) {
167        IFilesystem* filesystem = GetFilesystem(protocol);
168        if (filesystem) {
169            return filesystem->DeleteFile(path);
170        }
171    }
172
173    return false;
174}
175
176bool FileManager::Rename(const string_view fromUri, const string_view toUri)
177{
178    string_view fromProtocol;
179    string_view fromPath;
180    auto from = FixPath(fromUri);
181    if (ParseUri(from, fromProtocol, fromPath)) {
182        string_view toProtocol;
183        string_view toPath;
184        auto to = FixPath(toUri);
185        if (ParseUri(to, toProtocol, toPath)) {
186            if (fromProtocol == toProtocol) {
187                IFilesystem* filesystem = GetFilesystem(fromProtocol);
188                if (filesystem) {
189                    return filesystem->Rename(fromPath, toPath);
190                }
191            } else {
192                CORE_LOG_E("Rename requires both uris have same protocol");
193            }
194        }
195    }
196
197    return false;
198}
199
200IDirectory::Entry FileManager::GetEntry(const string_view uriIn)
201{
202    string_view protocol;
203    string_view path;
204    auto uri = FixPath(uriIn);
205    if (ParseUri(uri, protocol, path)) {
206        IFilesystem* filesystem = GetFilesystem(protocol);
207        if (filesystem) {
208            return filesystem->GetEntry(path);
209        }
210        CORE_LOG_E("Failed to get entry for uri, no file system for uri: '%s'", string(uri).c_str());
211    } else {
212        CORE_LOG_E("Failed to get entry for uri, invalid uri: '%s'", string(uri).c_str());
213    }
214
215    return {};
216}
217IDirectory::Ptr FileManager::OpenDirectory(const string_view uriIn)
218{
219    string_view protocol;
220    string_view path;
221    auto uri = FixPath(uriIn);
222    if (ParseUri(uri, protocol, path)) {
223        IFilesystem* filesystem = GetFilesystem(protocol);
224        if (filesystem) {
225            return filesystem->OpenDirectory(path);
226        }
227        CORE_LOG_E("Failed to open directory, no file system for uri: '%s'", string(uri).c_str());
228    } else {
229        CORE_LOG_E("Failed to open directory, invalid uri: '%s'", string(uri).c_str());
230    }
231
232    return IDirectory::Ptr();
233}
234
235IDirectory::Ptr FileManager::CreateDirectory(const string_view uriIn)
236{
237    string_view protocol;
238    string_view path;
239    auto uri = FixPath(uriIn);
240    if (ParseUri(uri, protocol, path)) {
241        IFilesystem* filesystem = GetFilesystem(protocol);
242        if (filesystem) {
243            return filesystem->CreateDirectory(path);
244        }
245        CORE_LOG_E("Failed to create directory, no file system for uri: '%s'", string(uri).c_str());
246    } else {
247        CORE_LOG_E("Failed to create directory, invalid uri: '%s'", string(uri).c_str());
248    }
249
250    return IDirectory::Ptr();
251}
252
253bool FileManager::DeleteDirectory(const string_view uriIn)
254{
255    string_view protocol;
256    string_view path;
257    auto uri = FixPath(uriIn);
258    if (ParseUri(uri, protocol, path)) {
259        IFilesystem* filesystem = GetFilesystem(protocol);
260        if (filesystem) {
261            return filesystem->DeleteDirectory(path);
262        }
263    }
264
265    return false;
266}
267
268void FileManager::RegisterFilesystem(const string_view protocol, IFilesystem::Ptr filesystem)
269{
270    CORE_ASSERT_MSG(filesystems_.find(protocol) == filesystems_.cend(), "File system already registered");
271
272    filesystems_[protocol] = move(filesystem);
273}
274
275void FileManager::UnregisterFilesystem(const string_view protocol)
276{
277    const auto iterator = filesystems_.find(protocol);
278    if (iterator != filesystems_.end()) {
279        filesystems_.erase(iterator);
280    }
281}
282
283void FileManager::RegisterAssetPath(const string_view uriIn)
284{
285    auto uri = FixPath(uriIn);
286    RegisterPath("assets", uri, false);
287}
288
289void FileManager::UnregisterAssetPath(const string_view uriIn)
290{
291    auto uri = FixPath(uriIn);
292    UnregisterPath("assets", uri);
293}
294
295vector<string> FileManager::GetAbsolutePaths(const string_view uriIn) const
296{
297    vector<string> ret;
298    string_view protocol;
299    string_view path;
300    auto uri = FixPath(uriIn);
301    if (ParseUri(uri, protocol, path)) {
302        const IFilesystem* filesystem = GetFilesystem(protocol);
303        if (filesystem) {
304            // a single URI path can be found in several paths in a proxy filesystem
305            auto uriPaths = filesystem->GetUriPaths(path);
306            for (auto& uriPath : uriPaths) {
307                if (uriPath.find("file://") == string::npos) {
308                    auto tmp = GetAbsolutePaths(uriPath);
309                    ret.insert(ret.cend(), tmp.cbegin(), tmp.cend());
310                } else {
311                    ret.push_back(move(uriPath));
312                }
313            }
314        }
315    }
316    std::transform(ret.begin(), ret.end(), ret.begin(), [](const string& uri) {
317        string_view protocol;
318        string_view path;
319        if (ParseUri(uri, protocol, path)) {
320            return StdDirectory::ResolveAbsolutePath(path, true);
321        }
322        return uri;
323    });
324
325    return ret;
326}
327
328bool FileManager::RegisterPath(const string_view protocol, const string_view uriIn, bool prepend)
329{
330    auto uri = FixPath(uriIn);
331    // Check if the proxy protocol exists already.
332    auto it = proxyFilesystems_.find(protocol);
333    if (it != proxyFilesystems_.end()) {
334        // Yes, add the new search path to it.
335        if (prepend) {
336            it->second->PrependSearchPath(uri);
337        } else {
338            it->second->AppendSearchPath(uri);
339        }
340        return true;
341    }
342
343    // Check if the protocol is already declared..
344    const auto itp = filesystems_.find(protocol);
345    if (itp != filesystems_.end()) {
346        // Okay there is a protocol handler already, we can't add paths to non-proxy protocols.
347        CORE_LOG_W("Tried to register a path to non-proxy filesystem. protocol [%s] uriIn [%s]",
348            string(protocol).c_str(), string(uriIn).c_str());
349        return false;
350    }
351
352    // Create new proxy protocol handler.
353    auto pfs = make_unique<ProxyFilesystem>(*this, uri);
354    proxyFilesystems_[protocol] = pfs.get();
355    RegisterFilesystem(protocol, IFilesystem::Ptr { pfs.release() });
356    return true;
357}
358
359void FileManager::UnregisterPath(const string_view protocol, const string_view uriIn)
360{
361    auto uri = FixPath(uriIn);
362    auto it = proxyFilesystems_.find(protocol);
363    if (it != proxyFilesystems_.end()) {
364        it->second->RemoveSearchPath(uri);
365    }
366}
367
368IFilesystem* FileManager::GetFilesystem(const string_view protocol) const
369{
370    const auto it = filesystems_.find(protocol);
371    if (it != filesystems_.end()) {
372        return it->second.get();
373    }
374
375    return nullptr;
376}
377
378IFilesystem::Ptr FileManager::CreateROFilesystem(const void* const data, uint64_t size)
379{
380    return IFilesystem::Ptr { new RoFileSystem(data, static_cast<size_t>(size)) };
381}
382CORE_END_NAMESPACE()
383