Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
crawler.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "crawler.hh"
3#include "jsonipc/jsonipc.hh"
4#include "path.hh"
5#include "platform.hh"
6#include "unicode.hh"
7#include "strings.hh"
8#include "internal.hh"
9#include <dirent.h>
10#include <fcntl.h> // AT_NO_AUTOMOUNT
11#include <unistd.h>
12#include <sys/stat.h>
13#include <sys/types.h>
14#include <filesystem>
15namespace Fs = std::filesystem;
16
17#define CDEBUG(...) Ase::debug ("crawler", __VA_ARGS__)
18
19namespace Ase {
20
21// == ResourceCrawler ==
22ResourceCrawler::ResourceCrawler() :
23 folder (this, "folder"),
24 entries (this, "entries")
25{}
26
27// == FileCrawler ==
28FileCrawler::FileCrawler (const String &cwd, bool constraindir, bool constrainfile) :
29 cwd_ ("/"), constraindir_ (constraindir), constrainfile_ (constrainfile)
30{
31 if (!cwd.empty())
32 assign_ (encodefs (cwd), false, false);
33}
34
36ResourceS
37FileCrawler::list_entries ()
38{
39 ResourceS rs;
40 DIR *dir = opendir (cwd_.c_str());
41 if (!dir)
42 CDEBUG ("%s: opendir('%s'): %s", __func__, cwd_, strerror (errno));
43 return_unless (dir, rs);
44 String cwdfile = cwd_ + "/";
45 for (struct dirent *de = readdir (dir); de; de = readdir (dir))
46 {
47 bool is_dir; // = de->d_type == DT_DIR;
48 bool is_reg; // = de->d_type == DT_REG;
49 ssize_t size = -1;
50 int64 mtime = 0;
51 if (true) // (de->d_type == DT_LNK || de->d_type == DT_UNKNOWN)
52 {
53 struct stat statbuf = { 0, };
54 if (0 != fstatat (dirfd (dir), de->d_name, &statbuf, AT_NO_AUTOMOUNT))
55 continue;
56 is_dir = S_ISDIR (statbuf.st_mode);
57 is_reg = S_ISREG (statbuf.st_mode);
58 size = statbuf.st_size;
59 mtime = statbuf.st_mtim.tv_sec * 1000 + statbuf.st_mtim.tv_nsec / 1000000;
60 }
61 if (!is_reg && !is_dir)
62 continue;
63 Resource r {
64 .type = is_dir ? ResourceType::FOLDER : ResourceType::FILE,
65 .label = displayfs (encodefs (de->d_name)),
66 .uri = encodefs (cwdfile + de->d_name) + (is_dir ? "/" : ""),
67 .mtime = mtime };
68 r.size = is_dir && size > 0 ? -size : size;
69 rs.push_back (r);
70 }
71 closedir (dir);
72 return rs;
73}
74
77FileCrawler::expand_fsdir (const String &fsdir)
78{
79 if (fsdir == ".")
80 return Path::dir_terminate (cwd_);
81 if (string_toupper (fsdir) == "DEMO")
82 return Path::dir_terminate (anklang_runpath (RPath::DEMODIR));
83 String dir = Path::xdg_dir (fsdir);
84 if (!dir.empty())
85 return Path::dir_terminate (dir);
86 return "/";
87}
88
91FileCrawler::current_folder ()
92{
93 Resource r {
94 .type = ResourceType::FOLDER,
95 .label = displayfs (encodefs (Path::basename (cwd_))),
96 .uri = encodefs (Path::dir_terminate (cwd_)),
97 .mtime = 0 };
98 struct stat statbuf = { 0, };
99 if (lstat (cwd_.c_str(), &statbuf) == 0)
100 {
101 r.size = statbuf.st_size;
102 r.mtime = statbuf.st_mtim.tv_sec * 1000 + statbuf.st_mtim.tv_nsec / 1000000;
103 }
104 return r;
105}
106
108FileCrawler::get_folder() const
109{
110 return const_cast<FileCrawler&> (*this).current_folder();
111}
112
113void
114FileCrawler::set_folder (const Resource &newfolder)
115{
116 assign_ (newfolder.uri, false);
117}
118
119ResourceS
120FileCrawler::get_entries() const
121{
122 return const_cast<FileCrawler&> (*this).list_entries();
123}
124
125void
126FileCrawler::set_entries (const ResourceS &newentries)
127{
128 // assignment not supported for FileCrawler
129}
130
132FileCrawler::String2
133FileCrawler::assign_ (const String &utf8path, bool existingfile, bool notify)
134{
135 String dir = decodefs (utf8path);
136 // tilde + uppercase features some special directory expansions
137 if (dir[0] == '~' &&
138 dir.find ("/") == dir.npos &&
139 string_isupper (dir))
140 {
141 if (dir == "~DEMO")
142 dir = Path::dir_terminate (anklang_runpath (RPath::DEMODIR));
143 else
144 dir = Path::xdg_dir (&dir[1]);
145 if (dir.empty() || dir == "/") // failed to expand special word
146 dir = decodefs (utf8path);
147 }
148 // ~USER expansion
149 Fs::path p = Path::expand_tilde (dir);
150 // make absolute
151 if (!p.is_absolute())
152 {
153 Fs::path root = cwd_;
154 p = root / p;
155 }
156 // normalize, remove /// /./ ..
157 p = p.lexically_normal();
158 // return existing file or dir with slash
159 String filename = "";
160 if (Path::check (p, "d")) // isdir?
161 cwd_ = Path::dir_terminate (p);
162 else if (Path::check (p, "e")) { // exists?
163 filename = p.filename();
164 cwd_ = p.parent_path();
165 } else if (constraindir_) { // find existing dir
166 while (p.string().size() > 1 &&
167 !Path::check (p, "d")) {
168 filename = !existingfile || Path::check (p, "e") ? p.filename() : "";
169 p = p.parent_path();
170 }
171 cwd_ = p;
172 } else {
173 filename = !existingfile || Path::check (p, "e") ? p.filename() : "";
174 cwd_ = p.parent_path();
175 }
176 // strip terminating slashes from cwd_
177 while (cwd_.size() > 1 && cwd_.back() == '/')
178 cwd_.resize (cwd_.size() - 1);
179 if (notify)
180 {
181 folder.notify();
182 entries.notify();
183 emit_notify ("current");
184 emit_notify ("entries");
185 }
186 return { cwd_, filename };
187}
188
190String
191FileCrawler::canonify_fspath (const String &fscwd, const String &fsfragment, bool constraindir, bool constrainfile)
192{
193 // expansions
194 Fs::path p = Path::expand_tilde (fsfragment);
195 // make absolute
196 if (!p.is_absolute())
197 {
198 Fs::path root = fscwd;
199 if (!root.is_absolute())
200 root = cwd_ / root;
201 p = root / p;
202 }
203 // normalize, remove /// /./ ..
204 p = p.lexically_normal();
205 // return existing file or dir with slash
206 if (Path::check (p, "d")) // isdir?
207 return Path::dir_terminate (p);
208 if (Path::check (p, "e")) // exists?
209 return p;
210 // force existing directory
211 Fs::path d = p.parent_path(), f = p.filename();
212 if (constraindir)
213 while (d.relative_path() != "" && !Path::check (d, "d"))
214 d = d.parent_path();
215 p = d / f;
216 // force existing or empty file
217 if (constrainfile && !Path::check (p, "e"))
218 p = d;
219 // return dirs with slash
220 if (Path::check (p, "d"))
221 return Path::dir_terminate (p);
222 return p;
223}
224
227FileCrawler::canonify (const String &utf8cwd, const String &utf8fragment, bool constraindir, bool constrainfile)
228{
229 const String utf8path = encodefs (canonify_fspath (decodefs (utf8cwd), decodefs (utf8fragment), constraindir, constrainfile));
230 Resource r {
231 .type = string_endswith (utf8path, "/") ? ResourceType::FOLDER : ResourceType::FILE,
232 .label = displayfs (utf8path),
233 .uri = utf8path,
234 .mtime = 0 };
235 return r;
236}
237
238} // Ase
239
240#include "testing.hh"
241
242TEST_INTEGRITY (crawler_tests);
244static void
245crawler_tests()
246{
247 using namespace Ase;
248 FileCrawlerP cp = FileCrawler::make_shared ("/dev", true, false);
249 FileCrawler &c = *cp;
250 String r, e;
251 r = c.canonify_fspath ("", "/dev/N°…Diŕ/N°…Fílė", 1, 1); e = "/dev/"; TCMP (r, ==, e);
252 r = c.canonify_fspath (".", ".", 1, 1); e = "/dev/"; TCMP (r, ==, e);
253 r = c.canonify_fspath ("", "/dev/N°…Diŕ/null", 1, 1); e = "/dev/null"; TCMP (r, ==, e);
254 r = c.canonify_fspath ("", "/tmp/N°…Diŕ/N°…Fílė", 1, 0); e = "/tmp/N°…Fílė"; TCMP (r, ==, e);
255 r = c.canonify_fspath ("", "/tmp/N°…Diŕ//.//", 0, 0); e = "/tmp/N°…Diŕ/"; TCMP (r, ==, e);
256 r = c.canonify_fspath ("", "/tmp/N°…Diŕ//..//", 0, 0); e = "/tmp/"; TCMP (r, ==, e);
257 r = c.canonify_fspath ("N°…Diŕ", "N°…Fílė", 1, 1); e = "/dev/"; TCMP (r, ==, e);
258 r = c.canonify_fspath ("/N°…Diŕ", "N°…Fílė", 1, 1); e = "/"; TCMP (r, ==, e);
259}
Class implementing a file system crawler.
Definition crawler.hh:10
ResourceS list_entries()
List all entries in the current folder.
Definition crawler.cc:37
Resource current_folder()
Return the current folder.
Definition crawler.cc:91
closedir
dirfd
T empty(T... args)
errno
opendir
T filename(T... args)
T find(T... args)
stat
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:77
T is_absolute(T... args)
T lexically_normal(T... args)
String cwd()
Return the current working directoy, including symlinks used in $PWD if available.
Definition path.cc:662
The Anklang C++ API namespace.
Definition api.hh:9
ResourceType type
Resource classification.
Definition api.hh:360
bool string_isupper(const String &str)
Check if all string characters are Unicode upper case characters.
Definition strings.cc:166
int64_t int64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:29
String string_toupper(const String &str)
Convert all string characters into Unicode upper case characters.
Definition strings.cc:156
std::string decodefs(const std::string &utf8str)
Decode UTF-8 string back into file system path representation, extracting surrogate code points as by...
Definition unicode.cc:131
std::string anklang_runpath(RPath rpath, const String &segment)
Retrieve various resource paths at runtime.
Definition platform.cc:58
String uri
Unique resource identifier.
Definition api.hh:362
std::string displayfs(const std::string &utf8str)
Convert UTF-8 encoded file system path into human readable display format, the conversion is lossy bu...
Definition unicode.cc:150
bool string_endswith(const String &string, const String &fragment)
Returns whether string ends with fragment.
Definition strings.cc:863
std::string encodefs(const std::string &fschars)
Encode a file system path consisting of bytes into UTF-8, using surrogate code points to store non UT...
Definition unicode.cc:112
Description of a resource, possibly nested.
Definition api.hh:359
T parent_path(T... args)
readdir
T relative_path(T... args)
T size(T... args)
strerror
typedef ssize_t
#define TCMP(a, cmp, b)
Compare a and b according to operator cmp, verbose on failiure.
Definition testing.hh:23