@@ -3,142 +3,32 @@ module danode.filesystem;
33import danode.imports;
44import danode.statuscode : StatusCode;
55import danode.mimetypes : mime;
6- import danode.payload : Payload, PayloadType;
6+ import danode.payload : Payload, FilePayload, PayloadType;
77import danode.functions : has, isCGI;
88import danode.log : custom, info, Log , warning, trace, cverbose, NOTSET , NORMAL , DEBUG ;
99
10- class FileInfo : Payload {
11- public :
12- bool deflate = false ;
13- private :
14- string path;
15- SysTime btime;
16- bool buffered = false ;
17- char [] buf = null ;
18- char [] encbuf = null ;
19- File * fp = null ;
20-
21- public :
22- this (string path) { this .path = path; }
23-
24- // Does the file needs updating
25- final bool needsupdate (size_t buffersize = 4096 ) {
26- if ( fitsInBuffer(buffersize) && needsBuffer() ) {
27- if (! buffered) {
28- info(" need to buffer file record: %s" , path);
29- return true ;
30- }
31- if (mtime > btime) {
32- info(" re-buffer stale file record: %s" , path);
33- return true ;
34- }
35- }
36- return false ;
37- }
38-
39- // Does the file fit in the buffer
40- final bool fitsInBuffer (size_t buffersize = 4096 ) {
41- if (fileSize() > 0 && fileSize() < buffersize){ return (true ); }
42- return (false );
43- }
44-
45- // Buffer the file
46- final void buffer () { synchronized {
47- if (buf is null ) buf = new char [](fileSize());
48- buf.length = fileSize();
49- try {
50- if (fp is null ) fp = new File (path, " rb" );
51- fp.open(path, " rb" );
52- fp.rawRead(buf);
53- fp.close();
54- } catch (Exception e) {
55- warning(" exception during buffering '%s': %s" , path, e.msg);
56- return ;
57- }
58- try {
59- encbuf = cast (char [])( compress(buf, 9 ) );
60- } catch (Exception e) {
61- warning(" exception during compressing '%s': %s" , path, e.msg);
62- }
63- btime = Clock .currTime();
64- trace(" buffered %s: %d|%d bytes" , path, fileSize(), encbuf.length);
65- buffered = true ;
66- } }
67-
68- final @property string content(){ return ( to! string (bytes(0 , length)) ); }
69- final @property bool realfile() const { return (path.exists()); }
70- final @property bool hasEncodedVersion() const { return (encbuf ! is null ); }
71- final @property bool needsBuffer() { return (! path.isCGI()); }
72- final @property SysTime mtime() const { if (! realfile){ return btime; } return path.timeLastModified(); }
73- final @property long ready() { return (true ); }
74- final @property PayloadType type() const { return (PayloadType.Message); }
75- final @property ptrdiff_t fileSize() const { if (! realfile){ return - 1 ; } return to! ptrdiff_t (path.getSize()); }
76- final @property long buffersize() const { return cast (long )(buf.length); }
77- final @property string mimetype() const { return mime(path); }
78- final @property StatusCode statuscode() const { return StatusCode.Ok; }
79-
80- final @property ptrdiff_t length() const {
81- if (hasEncodedVersion && deflate) return (encbuf.length);
82- return (fileSize());
83- }
84-
85- // Send the file as a stream
86- final char [] asStream (ptrdiff_t from, ptrdiff_t maxsize = 1024 ) {
87- if (buf is null ) buf = new char [](maxsize);
88- char [] slice = [];
89- if (cverbose >= DEBUG && from == 0 ) write(" [STREAM] ." );
90- if (from >= fileSize()) {
91- trace(" from >= filesize, are we still trying to send?" );
92- return ([]);
93- }
94- try {
95- if (fp is null ) fp = new File (path, " rb" );
96- fp.open(path, " rb" );
97- if (fp.isOpen()) {
98- fp.seek(from);
99- slice = fp.rawRead! char (buf);
100- fp.close();
101- if (cverbose >= DEBUG ) write(" ." );
102- if (cverbose >= DEBUG && (from + slice.length) >= fileSize()) write(" \n " );
103- }
104- } catch (Exception e) {
105- warning(" exception %s while streaming file: %s" , e.msg, path);
106- }
107- return (slice);
108- }
109-
110- final char [] bytes (ptrdiff_t from, ptrdiff_t maxsize = 1024 ){ synchronized {
111- if (! realfile) { return []; }
112- trace(" file provided is a real file" );
113- if (needsupdate) { buffer(); }
114- if (! buffered) {
115- return (asStream(from, maxsize));
116- } else {
117- if (hasEncodedVersion && deflate) {
118- if (from < encbuf.length) return ( encbuf[from .. to! ptrdiff_t (min(from+ maxsize, $))] );
119- } else {
120- if (from < buf.length) return ( buf[from .. to! ptrdiff_t (min(from+ maxsize, $))] );
121- }
122- }
123- return ([]);
124- } }
125- }
126-
10+ /* Domain name structure containing files in that domain
11+ Domains are loaded by the FileSystem from the -wwwRoot variable (set to www/ by default)
12+ Note 1: Domains are named as requested by the HTTP client so SSL keynames must match domainnames (e.g.: localhost / 127.0.0.1 / XX.XX.XX.XX or xxx.xx)
13+ Note 2: ./www/localhost existing is required for unit testing */
12714struct Domain {
128- FileInfo [string ] files;
15+ FilePayload [string ] files;
12916 long entries;
13017 long buffered;
13118
13219 @property long buffersize() const { long sum = 0 ; foreach (ref f; files.byKey ){ sum += files[f].buffersize(); } return sum; }
13320 @property long size() const { long sum = 0 ; foreach (ref f; files.byKey ){ sum += files[f].length(); } return sum; }
13421}
13522
23+ /* File system class that manages the underlying domains
24+ Note: Should this really be thread synchronized access ?
25+ */
13626class FileSystem {
13727 private :
138- string root;
139- Domain[string ] domains;
140- Log logger;
141- size_t maxsize;
28+ string root;
29+ Domain[string ] domains;
30+ Log logger;
31+ size_t maxsize;
14232
14333 public :
14434 this (Log logger, string root = " ./www/" , size_t maxsize = 1024 * 512 ){
@@ -148,24 +38,24 @@ class FileSystem {
14838 scan();
14939 }
15040
151- // Scan the whole filesystem
41+ /* Scan the whole filesystem for changes */
15242 final void scan (){ synchronized {
15343 foreach (DirEntry d; dirEntries(root, SpanMode.shallow)){ if (d.isDir()){
15444 domains[d.name] = scan(d.name);
15545 } }
15646 } }
15747
158- // Scan a single directory
48+ /* Scan a single folder */
15949 final Domain scan (string dname){ synchronized {
16050 Domain domain;
16151 foreach (DirEntry f; dirEntries(dname, SpanMode.depth)) {
16252 if (f.isFile()) {
16353 string shortname = replace(f.name[dname.length .. $], " \\ " , " /" );
16454 custom(1 , " SCAN" , " file: %s -> %s" , f.name, shortname);
16555 if (! domain.files.has(shortname)) {
166- domain.files[shortname] = new FileInfo (f.name);
56+ domain.files[shortname] = new FilePayload (f.name, maxsize );
16757 domain.entries++ ;
168- if (domain.files[shortname].needsupdate(maxsize )) {
58+ if (domain.files[shortname].needsupdate()) {
16959 domain.files[shortname].buffer();
17060 domain.buffered++ ;
17161 }
@@ -177,27 +67,35 @@ class FileSystem {
17767 return (domain);
17868 } }
17969
180- // Get the localroot of the domain
181- final string localroot (string hostname) const { return (format(" %s%s" ,this .root, hostname)); }
70+ /* Get the localroot of the domain (TODO: Is there a bug, did I require that this.root should always end in a '/' ?) */
71+ final string localroot (string hostname) const { return (format(" %s%s" , this .root, hostname)); }
18272
183- // Get the file at path from the localroot
184- final FileInfo file (string localroot, string path){ synchronized {
185- if (! domains[localroot].files.has(path) && exists(format(" %s%s" , localroot, path))){
186- custom(1 , " SCAN" , " new file %s, rescanning index: %s" , path, localroot);
73+ /* Get the FilePayload at path from the localroot, with update check on buffers */
74+ final FilePayload file (string localroot, string path){ synchronized {
75+ // New file created after last scan ? -> Scan the whole folder for changes
76+ if (! domains[localroot].files.has(path) && exists(format(" %s%s" , localroot, path))) {
77+ custom(1 , " SCAN" , " New file %s, rescanning index: %s" , path, localroot);
18778 domains[localroot] = scan(localroot);
18879 }
189- if (domains[localroot].files.has(path)) return (domains[localroot].files[path]);
190- return new FileInfo(" " );
80+ // File exists, buffer the individual file if modified after buffer date
81+ if (domains[localroot].files.has(path)) {
82+ if (domains[localroot].files[path].needsupdate) domains[localroot].files[path].buffer();
83+ return (domains[localroot].files[path]);
84+ }
85+ custom(1 , " SCAN" , " should not be here not in index, but exists %s, %s" , path, localroot);
86+ return new FilePayload(" " , maxsize);
19187 } }
19288
193- // Rebuffer all files
89+ /* Rebuffer all file domains from disk,
90+ By reusing domain keys so, we don't buffer new domains. This is ok since we would need to load SSL */
19491 final void rebuffer () {
19592 foreach (ref d; domains.byKey ){ foreach (ref f; domains[d].files.byKey ){
19693 domains[d].files[f].buffer();
19794 } }
19895 }
19996}
20097
98+ /* Basic unit-tests should be extended */
20199unittest {
202100 custom(0 , " FILE" , " %s" , __FILE__ );
203101 Log logger = new Log (NORMAL );
0 commit comments