1 /** @file fileinfo.c File information.
2
3 @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4
5 @par License
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9
10 1. Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16 <small>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</small>
26 */
27
28 #include "the_Foundation/fileinfo.h"
29 #include "the_Foundation/file.h"
30 #include "the_Foundation/time.h"
31 #include "the_Foundation/path.h"
32
33 #include <stdlib.h>
34 #if defined (iPlatformWindows) || defined (iPlatformMsys)
35 # define iHaveMsdirent 1
36 # include "platform/win32/wide.h"
37 # include "platform/win32/msdirent.h"
38
39 # if defined (iPlatformMsys)
40 # define R_OK 0
41
access(const char * path,int mode)42 static int access(const char *path, int mode) {
43 const iString str = iStringLiteral(path);
44 iBlock *wpath = toUtf16_String(&str);
45 const DWORD attr = GetFileAttributesW(data_Block(wpath));
46 delete_Block(wpath);
47 iUnused(mode);
48 return (attr == INVALID_FILE_ATTRIBUTES ? -1 : 0);
49 }
50 # endif /* defined (iPlatformMsys) */
51
52 #else
53 # include <sys/stat.h>
54 # include <sys/types.h>
55 # if defined (iHaveSysDirent)
56 # include <sys/dirent.h>
57 # elif !defined (iPlatformHaiku)
58 # include <sys/dir.h>
59 # endif
60 # include <unistd.h>
61 # include <dirent.h>
62 #endif
63
64 #if defined (iPlatformLinux) || defined (iPlatformMsys) || defined (iPlatformCygwin) || defined (iPlatformHaiku)
65 # define st_mtimespec st_mtim
66 #endif
67
68 enum FileInfoFlags {
69 exists_FileInfoFlag = iBit(1),
70 directory_FileInfoFlag = iBit(2),
71 };
72
73 struct Impl_FileInfo {
74 iObject object;
75 iString *path;
76 iTime lastModified;
77 size_t size;
78 uint32_t flags;
79 };
80
81 iDefineClass(FileInfo)
82 iDefineObjectConstructionArgs(FileInfo, (const iString *path), path)
83
newCStr_FileInfo(const char * path)84 iFileInfo *newCStr_FileInfo(const char *path) {
85 iString str; initCStr_String(&str, path);
86 iFileInfo *info = new_FileInfo(&str);
87 deinit_String(&str);
88 return info;
89 }
90
new_FileInfo_(void)91 iFileInfo *new_FileInfo_(void) {
92 iFileInfo *d = iNew(FileInfo);
93 d->path = new_String();
94 iZap(d->lastModified);
95 d->size = 0;
96 d->flags = 0;
97 return d;
98 }
99
init_FileInfo(iFileInfo * d,const iString * path)100 void init_FileInfo(iFileInfo *d, const iString *path) {
101 d->path = copy_String(path);
102 d->flags = 0;
103 struct stat st;
104 if (!stat(cstr_String(d->path), &st)) {
105 #if defined (iPlatformWindows)
106 d->lastModified.ts = (struct timespec){ .tv_sec = st.st_mtime };
107 #else
108 d->lastModified.ts = st.st_mtimespec;
109 #endif
110 d->size = st.st_size;
111 d->flags |= exists_FileInfoFlag;
112 if (st.st_mode & S_IFDIR) d->flags |= directory_FileInfoFlag;
113 }
114 else {
115 iZap(d->lastModified);
116 d->size = iInvalidSize;
117 }
118 }
119
initDirEntry_FileInfo_(iFileInfo * d,const iString * dirPath,struct dirent * ent)120 static iBool initDirEntry_FileInfo_(iFileInfo *d, const iString *dirPath, struct dirent *ent) {
121 iString entryName;
122 #if defined (iPlatformApple)
123 initLocalCStrN_String(&entryName, ent->d_name, ent->d_namlen);
124 #elif defined (iHaveMsdirent)
125 initCStrN_String(&entryName, ent->d_name, ent->d_namlen); /* UTF-8 name */
126 #else
127 initLocalCStr_String(&entryName, ent->d_name);
128 #endif
129 /* Check for ignored entries. */
130 if (!cmp_String(&entryName, "..") || !cmp_String(&entryName, ".")) {
131 deinit_String(&entryName);
132 return iFalse;
133 }
134 iString *full = concat_Path(dirPath, &entryName);
135 clean_Path(full);
136 set_String(d->path, full);
137 delete_String(full);
138 deinit_String(&entryName);
139
140 d->flags = exists_FileInfoFlag;
141 #if defined (iPlatformHaiku)
142 struct stat s;
143 stat(ent->d_name, &s);
144 if (S_ISDIR(s.st_mode)) {
145 #else
146 if (ent->d_type == DT_DIR) {
147 #endif
148 d->flags |= directory_FileInfoFlag;
149 d->size = 0;
150 }
151 else {
152 d->size = iInvalidSize; // Unknown at this time.
153 }
154 return iTrue;
155 }
156
157 void deinit_FileInfo(iFileInfo *d) {
158 delete_String(d->path);
159 }
160
161 iBool exists_FileInfo(const iFileInfo *d) {
162 return (d->flags & exists_FileInfoFlag) != 0;
163 }
164
165 const iString *path_FileInfo(const iFileInfo *d) {
166 return d->path;
167 }
168
169 size_t size_FileInfo(const iFileInfo *d) {
170 if (d->size == iInvalidSize) {
171 iConstCast(iFileInfo *, d)->size = fileSize_FileInfo(d->path);
172 }
173 return d->size;
174 }
175
176 iBool isDirectory_FileInfo(const iFileInfo *d) {
177 return (d->flags & directory_FileInfoFlag) != 0;
178 }
179
180 iTime lastModified_FileInfo(const iFileInfo *d) {
181 if (!isValid_Time(&d->lastModified)) {
182 struct stat st;
183 if (!stat(cstr_String(d->path), &st)) {
184 #if defined (iPlatformWindows)
185 iConstCast(iFileInfo *, d)->lastModified.ts = (struct timespec){
186 .tv_sec = st.st_mtime };
187 #else
188 iConstCast(iFileInfo *, d)->lastModified.ts = st.st_mtimespec;
189 #endif
190 }
191 }
192 return d->lastModified;
193 }
194
195 iDirFileInfo *directoryContents_FileInfo(const iFileInfo *fileInfo) {
196 iDirFileInfo *d = iNew(DirFileInfo);
197 initInfo_DirFileInfo(d, fileInfo);
198 return d;
199 }
200
201 iFile *open_FileInfo(const iFileInfo *d, int modeFlags) {
202 iFile *f = new_File(path_FileInfo(d));
203 open_File(f, modeFlags);
204 return f;
205 }
206
207 iBool fileExists_FileInfo(const iString *path) {
208 return fileExistsCStr_FileInfo(cstr_String(path));
209 }
210
211 iBool fileExistsCStr_FileInfo(const char *path) {
212 return !access(path, R_OK);
213 }
214
215 size_t fileSize_FileInfo(const iString *path) {
216 return fileSizeCStr_FileInfo(cstr_String(path));
217 }
218
219 size_t fileSizeCStr_FileInfo(const char *path) {
220 size_t size = iInvalidSize;
221 #if defined (iHaveMsdirent)
222 iBeginCollect();
223 const wchar_t *wpath = toWide_CStr_(path);
224 HANDLE f = CreateFileW(wpath,
225 FILE_READ_ATTRIBUTES,
226 FILE_SHARE_READ,
227 NULL,
228 OPEN_EXISTING,
229 0,
230 NULL);
231 if (f != INVALID_HANDLE_VALUE) {
232 BY_HANDLE_FILE_INFORMATION info;
233 GetFileInformationByHandle(f, &info);
234 size = (size_t) info.nFileSizeLow | (((size_t) info.nFileSizeHigh) << 32);
235 CloseHandle(f);
236 }
237 else {
238 if (GetFileAttributesW(wpath) & FILE_ATTRIBUTE_DIRECTORY) {
239 size = 0;
240 }
241 }
242 iEndCollect();
243 #else
244 struct stat st;
245 if (!stat(path, &st)) {
246 size = (size_t) st.st_size;
247 }
248 #endif
249 return size;
250 }
251
252 /*-------------------------------------------------------------------------------------*/
253
254 struct Impl_DirFileInfo {
255 iObject object;
256 const iFileInfo *dirInfo;
257 DIR *fd;
258 iFileInfo *entry;
259 };
260
261 iDefineClass(DirFileInfo)
262 iDefineObjectConstructionArgs(DirFileInfo, (const iString *path), path)
263
264 iDirFileInfo *newCStr_DirFileInfo(const char *path) {
265 iString str; initCStr_String(&str, path);
266 iDirFileInfo *d = new_DirFileInfo(&str);
267 deinit_String(&str);
268 return d;
269 }
270
271 void init_DirFileInfo(iDirFileInfo *d, const iString *path) {
272 iFileInfo *fileInfo = new_FileInfo(path);
273 initInfo_DirFileInfo(d, fileInfo);
274 iRelease(fileInfo);
275 d->entry = NULL;
276 }
277
278 void initInfo_DirFileInfo(iDirFileInfo *d, const iFileInfo *dir) {
279 if (isDirectory_FileInfo(dir)) {
280 d->fd = opendir(cstr_String(path_FileInfo(dir)));
281 d->dirInfo = ref_Object(dir);
282 }
283 else {
284 d->fd = NULL;
285 d->dirInfo = NULL;
286 }
287 d->entry = NULL;
288 }
289
290 void deinit_DirFileInfo(iDirFileInfo *d) {
291 if (d->fd) {
292 closedir(d->fd);
293 d->fd = NULL;
294 }
295 iRelease(d->entry);
296 deref_Object(d->dirInfo);
297 }
298
299 static iBool readNextEntry_DirFileInfo_(iDirFileInfo *d) {
300 iReleasePtr(&d->entry);
301 if (!d->fd) {
302 return iFalse;
303 }
304 for (;;) {
305 struct dirent *result = NULL;
306 #if defined (iPlatformApple)
307 struct dirent ent;
308 readdir_r(d->fd, &ent, &result);
309 #else
310 result = readdir(d->fd);
311 #endif
312 if (result) {
313 iReleasePtr(&d->entry);
314 d->entry = new_FileInfo_();
315 if (!initDirEntry_FileInfo_(d->entry, path_FileInfo(d->dirInfo), result)) {
316 continue;
317 }
318 return iTrue;
319 }
320 return iFalse;
321 }
322 }
323
324 void init_DirFileInfoIterator(iDirFileInfoIterator *d, iDirFileInfo *info) {
325 d->dir = info;
326 next_DirFileInfoIterator(d);
327 }
328
329 void next_DirFileInfoIterator(iDirFileInfoIterator *d) {
330 if (readNextEntry_DirFileInfo_(d->dir)) {
331 d->value = d->dir->entry;
332 }
333 else {
334 d->value = NULL;
335 }
336 }
337