Anklang-0.3.0.dev797+g4e3241f3 anklang-0.3.0.dev797+g4e3241f3
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{}
24
25// == FileCrawler ==
26FileCrawler::FileCrawler (const String &cwd, bool constraindir, bool constrainfile) :
27 cwd_ ("/"), constraindir_ (constraindir), constrainfile_ (constrainfile)
28{
29 if (!cwd.empty())
30 assign_ (encodefs (cwd), false, false);
31}
32
34ResourceS
35FileCrawler::list_entries ()
36{
37 ResourceS rs;
38 DIR *dir = opendir (cwd_.c_str());
39 if (!dir)
40 CDEBUG ("%s: opendir('%s'): %s", __func__, cwd_, strerror (errno));
41 return_unless (dir, rs);
42 String cwdfile = cwd_ + "/";
43 for (struct dirent *de = readdir (dir); de; de = readdir (dir))
44 {
45 bool is_dir; // = de->d_type == DT_DIR;
46 bool is_reg; // = de->d_type == DT_REG;
47 ssize_t size = -1;
48 int64 mtime = 0;
49 if (true) // (de->d_type == DT_LNK || de->d_type == DT_UNKNOWN)
50 {
51 struct stat statbuf = { 0, };
52 if (0 != fstatat (dirfd (dir), de->d_name, &statbuf, AT_NO_AUTOMOUNT))
53 continue;
54 is_dir = S_ISDIR (statbuf.st_mode);
55 is_reg = S_ISREG (statbuf.st_mode);
56 size = statbuf.st_size;
57 mtime = statbuf.st_mtim.tv_sec * 1000 + statbuf.st_mtim.tv_nsec / 1000000;
58 }
59 if (!is_reg && !is_dir)
60 continue;
61 Resource r {
62 .type = is_dir ? ResourceType::FOLDER : ResourceType::FILE,
63 .label = displayfs (encodefs (de->d_name)),
64 .uri = encodefs (cwdfile + de->d_name) + (is_dir ? "/" : ""),
65 .mtime = mtime };
66 r.size = is_dir && size > 0 ? -size : size;
67 rs.push_back (r);
68 }
69 closedir (dir);
70 return rs;
71}
72
75FileCrawler::expand_fsdir (const String &fsdir)
76{
77 if (fsdir == ".")
78 return Path::dir_terminate (cwd_);
79 if (string_toupper (fsdir) == "DEMO")
80 return Path::dir_terminate (anklang_runpath (RPath::DEMODIR));
81 String dir = Path::xdg_dir (fsdir);
82 if (!dir.empty())
83 return Path::dir_terminate (dir);
84 return "/";
85}
86
89FileCrawler::current_folder ()
90{
91 Resource r {
92 .type = ResourceType::FOLDER,
93 .label = displayfs (encodefs (Path::basename (cwd_))),
94 .uri = encodefs (Path::dir_terminate (cwd_)),
95 .mtime = 0 };
96 struct stat statbuf = { 0, };
97 if (lstat (cwd_.c_str(), &statbuf) == 0)
98 {
99 r.size = statbuf.st_size;
100 r.mtime = statbuf.st_mtim.tv_sec * 1000 + statbuf.st_mtim.tv_nsec / 1000000;
101 }
102 return r;
103}
104
106FileCrawler::folder() const
107{
108 return const_cast<FileCrawler&> (*this).current_folder();
109}
110
111void
112FileCrawler::folder (const Resource &newfolder)
113{
114 assign_ (newfolder.uri, false);
115}
116
117ResourceS
118FileCrawler::entries() const
119{
120 return const_cast<FileCrawler&> (*this).list_entries();
121}
122
123void
124FileCrawler::entries (const ResourceS &newentries)
125{
126 // assignment not supported for FileCrawler
127}
128
130FileCrawler::String2
131FileCrawler::assign_ (const String &utf8path, bool existingfile, bool notify)
132{
133 String dir = decodefs (utf8path);
134 // tilde + uppercase features some special directory expansions
135 if (dir[0] == '~' &&
136 dir.find ("/") == dir.npos &&
137 string_isupper (dir))
138 {
139 if (dir == "~DEMO")
140 dir = Path::dir_terminate (anklang_runpath (RPath::DEMODIR));
141 else
142 dir = Path::xdg_dir (&dir[1]);
143 if (dir.empty() || dir == "/") // failed to expand special word
144 dir = decodefs (utf8path);
145 }
146 // ~USER expansion
147 Fs::path p = Path::expand_tilde (dir);
148 // make absolute
149 if (!p.is_absolute())
150 {
151 Fs::path root = cwd_;
152 p = root / p;
153 }
154 // normalize, remove /// /./ ..
155 p = p.lexically_normal();
156 // return existing file or dir with slash
157 String filename = "";
158 if (Path::check (p, "d")) // isdir?
159 cwd_ = Path::dir_terminate (p);
160 else if (Path::check (p, "e")) { // exists?
161 filename = p.filename();
162 cwd_ = p.parent_path();
163 } else if (constraindir_) { // find existing dir
164 while (p.string().size() > 1 &&
165 !Path::check (p, "d")) {
166 filename = !existingfile || Path::check (p, "e") ? p.filename() : "";
167 p = p.parent_path();
168 }
169 cwd_ = p;
170 } else {
171 filename = !existingfile || Path::check (p, "e") ? p.filename() : "";
172 cwd_ = p.parent_path();
173 }
174 // strip terminating slashes from cwd_
175 while (cwd_.size() > 1 && cwd_.back() == '/')
176 cwd_.resize (cwd_.size() - 1);
177 if (notify)
178 {
179 emit_notify ("folder");
180 emit_notify ("entries");
181 emit_notify ("current");
182 emit_notify ("entries");
183 }
184 return { cwd_, filename };
185}
186
188String
189FileCrawler::canonify_fspath (const String &fscwd, const String &fsfragment, bool constraindir, bool constrainfile)
190{
191 // expansions
192 Fs::path p = Path::expand_tilde (fsfragment);
193 // make absolute
194 if (!p.is_absolute())
195 {
196 Fs::path root = fscwd;
197 if (!root.is_absolute())
198 root = cwd_ / root;
199 p = root / p;
200 }
201 // normalize, remove /// /./ ..
202 p = p.lexically_normal();
203 // return existing file or dir with slash
204 if (Path::check (p, "d")) // isdir?
205 return Path::dir_terminate (p);
206 if (Path::check (p, "e")) // exists?
207 return p;
208 // force existing directory
209 Fs::path d = p.parent_path(), f = p.filename();
210 if (constraindir)
211 while (d.relative_path() != "" && !Path::check (d, "d"))
212 d = d.parent_path();
213 p = d / f;
214 // force existing or empty file
215 if (constrainfile && !Path::check (p, "e"))
216 p = d;
217 // return dirs with slash
218 if (Path::check (p, "d"))
219 return Path::dir_terminate (p);
220 return p;
221}
222
225FileCrawler::canonify (const String &utf8cwd, const String &utf8fragment, bool constraindir, bool constrainfile)
226{
227 const String utf8path = encodefs (canonify_fspath (decodefs (utf8cwd), decodefs (utf8fragment), constraindir, constrainfile));
228 Resource r {
229 .type = string_endswith (utf8path, "/") ? ResourceType::FOLDER : ResourceType::FILE,
230 .label = displayfs (utf8path),
231 .uri = utf8path,
232 .mtime = 0 };
233 return r;
234}
235
236} // Ase
237
238#include "testing.hh"
239
240TEST_INTEGRITY (crawler_tests);
242static void
243crawler_tests()
244{
245 using namespace Ase;
246 FileCrawlerP cp = FileCrawler::make_shared ("/dev", true, false);
247 FileCrawler &c = *cp;
248 String r, e;
249 r = c.canonify_fspath ("", "/dev/N°…Diŕ/N°…Fílė", 1, 1); e = "/dev/"; TCMP (r, ==, e);
250 r = c.canonify_fspath (".", ".", 1, 1); e = "/dev/"; TCMP (r, ==, e);
251 r = c.canonify_fspath ("", "/dev/N°…Diŕ/null", 1, 1); e = "/dev/null"; TCMP (r, ==, e);
252 r = c.canonify_fspath ("", "/tmp/N°…Diŕ/N°…Fílė", 1, 0); e = "/tmp/N°…Fílė"; TCMP (r, ==, e);
253 r = c.canonify_fspath ("", "/tmp/N°…Diŕ//.//", 0, 0); e = "/tmp/N°…Diŕ/"; TCMP (r, ==, e);
254 r = c.canonify_fspath ("", "/tmp/N°…Diŕ//..//", 0, 0); e = "/tmp/"; TCMP (r, ==, e);
255 r = c.canonify_fspath ("N°…Diŕ", "N°…Fílė", 1, 1); e = "/dev/"; TCMP (r, ==, e);
256 r = c.canonify_fspath ("/N°…Diŕ", "N°…Fílė", 1, 1); e = "/"; TCMP (r, ==, e);
257}
Class implementing a file system crawler.
Definition crawler.hh:10
ResourceS list_entries()
List all entries in the current folder.
Definition crawler.cc:35
Resource current_folder()
Return the current folder.
Definition crawler.cc:89
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:73
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:79
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:354
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:356
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
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
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:353
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