1 /**
2  * TODO
3  */
4 module dash.utility.resources;
5 import dash.utility.output;
6 
7 import std.file, std.path, std.stdio, std.array, std.algorithm, std.datetime;
8 
9 /**
10  * Paths to the different resource files.
11  */
12 enum Resources : string
13 {
14     Home = "..",
15     Materials = Home ~ "/Materials",
16     Meshes = Home ~ "/Meshes",
17     Textures = Home ~ "/Textures",
18     Audio = Home ~ "/Audio",
19     Scripts = Home ~ "/Scripts",
20     Prefabs = Home ~ "/Prefabs",
21     Objects = Home ~ "/Objects",
22     Shaders = Home ~ "/Shaders",
23     UI = Home ~ "/UI",
24     ConfigDir = Home ~ "/Config",
25     ConfigFile = ConfigDir ~ "/Config",
26     InputBindings = ConfigDir ~ "/Input",
27 }
28 
29 /**
30  * Get all files in a given directory.
31  */
32 Resource[] scanDirectory( string path, string pattern = "" )
33 {
34     // Get absolute path to folder
35     string safePath = path.absolutePath().buildNormalizedPath();
36 
37     if( !safePath.exists() )
38     {
39         tracef( "%s does not exist.", path );
40         return [];
41     }
42 
43     // Start array
44     return ( pattern.length
45                 ? safePath.dirEntries( pattern, SpanMode.breadth ).array
46                 : safePath.dirEntries( SpanMode.breadth ).array )
47             .filter!( entry => entry.isFile )
48             .map!( entry => Resource( entry ) )
49             .array();
50 }
51 
52 enum internalResource = Resource( true );
53 
54 /**
55  * Represents a resource on the file system.
56  */
57 struct Resource
58 {
59 public:
60     @disable this();
61     
62     /**
63      * Creates a Resource from the given filepath.
64      *
65      * Params:
66      *  filePath =          The path of the file created.
67      */
68     this( string filePath )
69     {
70         assert( filePath.isFile(), "Invalid file name." );
71         _fullPath = filePath.absolutePath().buildNormalizedPath();
72     }
73 
74     /**
75      * Shuts down the File if it was instantiated.
76      */
77     ~this()
78     {
79         if( file && file.isOpen )
80             file.close();
81     }
82 
83     /// The full path to the file.
84     @property string fullPath()         { return _fullPath; }
85     /// The relative path from the executable to the file.
86     @property string relativePath()     { return _fullPath.relativePath(); }
87     /// The name of the file with its extension.
88     @property string fileName()         { return _fullPath.baseName(); }
89     /// The name of the file without its extension.
90     @property string baseFileName()     { return fileName().stripExtension(); }
91     /// The path to the directory containing the file.
92     @property string directory()        { return _fullPath.dirName(); }
93     /// The extensino of the file.
94     @property string extension()        { return _fullPath.extension(); }
95     /// Checks if the file still exists.
96     bool exists() @property             { return isInternal || fullPath.isFile(); }
97     /// Converts to a std.stdio.File
98     File* getFile( string mode = "r" )
99     {
100         if( isInternal )
101             return null;
102 
103         if( !file )
104             file = new File( _fullPath, mode );
105 
106         return file;
107     }
108 
109     /**
110      * Read the contents of the file.
111      *
112      * Returns: The contents of a file as a ubyte[].
113      */
114     ubyte[] read()
115     {
116         if( isInternal )
117             return [];
118 
119         markRead();
120         return cast(ubyte[])_fullPath.read();
121     }
122 
123     /**
124      * Read the contents of the file.
125      *
126      * Returns: The contents of a file as a string.
127      */
128     string readText()
129     {
130         if( isInternal )
131             return "";
132 
133         markRead();
134         return _fullPath.readText();
135     }
136 
137     /**
138      * Checks if the file has been modified since it was last loaded.
139      *
140      * Returns: Whether the last modified time is more recent than the time it was last read.
141      */
142     bool needsRefresh()
143     {
144         if( isInternal )
145             return false;
146 
147         return fullPath.timeLastModified > timeRead;
148     }
149 
150 private:
151     string _fullPath;
152     bool isInternal;
153     std.stdio.File* file;
154     SysTime timeRead;
155 
156     this( bool internal )
157     {
158         isInternal = internal;
159         _fullPath = "__internal";
160     }
161 
162     void markRead()
163     {
164         if( !isInternal )
165             timeRead = fullPath.timeLastModified();
166     }
167 }