1 module filemanager;
2 import nspire;
3 import nspire.device;
4 import std.stdio;
5 
6 int filemanagerFun(NSpire nspire, DeviceInfo deviceInfo, string[] args) {
7     int error;
8     
9     if (args.length < 1) {
10         help();
11         return 1;
12     }
13     
14     switch (args[0]) {
15         case "ls" : {
16             error = ls(nspire, args[1..$]);
17             break;
18         }
19         
20         case "info" : {
21             error = info(nspire, args[1..$]);
22             break;
23         }
24         
25         case "cp" : {
26             error = cp(nspire, deviceInfo, args[1..$]);
27             break;
28         }
29         
30         case "mv" : {
31             error = mv(nspire, deviceInfo, args[1..$]);
32             break;
33         }
34         
35         case "rm" : {
36             error = rm(nspire, deviceInfo, args[1..$]);
37             break;
38         }
39         
40         case "mkdir" : {
41             error = mkdir(nspire, args[1..$]);
42             break;
43         }
44         
45         case "rmdir" : {
46             error = rmdir(nspire, args[1..$]);
47             break;
48         }
49         
50         case "push" : {
51             error = push(nspire, deviceInfo, args[1..$]);
52             break;
53         }
54         
55         case "pull" : {
56             error = pull(nspire, deviceInfo, args[1..$]);
57             break;
58         }
59         
60         default : {
61             help();
62             return 1;
63         }
64     }
65     
66     return error;
67 } 
68 
69 int ls(NSpire nspire, string[] args) {
70     import std.datetime.systime;
71     
72     int error;
73 
74     if (args.length < 1) {
75         args ~= "/";
76     }
77     
78     auto dirs = nspire.dirList(args[0], error);
79     
80     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
81         writeln("Error: ", NSpire.errorStr(error));
82         return error;
83     }
84     
85     foreach (dir; dirs) {
86         auto name = dir.type == DirType.NSPIRE_DIR ? "\033[1m\x1B[94m" ~ dir.name ~ "\x1B[0m\033[22m" : dir.name;
87         writeln(dir.size, "\t", SysTime.fromUnixTime(dir.date), "\t", name);
88     }
89     
90     return 0;
91 }
92 
93 int info(NSpire nspire, string[] args) {
94     import std.datetime.systime;
95     import std.math : pow;
96     
97     int error;
98 
99     if (args.length < 1) {
100         help();
101         return 1;
102     }
103     
104     auto attr = nspire.getAttr(args[0], error);
105     
106     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
107         writeln("Error: ", NSpire.errorStr(error));
108         return error;
109     }
110     
111     writeln("Name: ", attr.name);
112     writeln("Size: ", attr.size, " bytes or ", cast(double) attr.size / pow(2, 10), " KiB");
113     writeln("Date: ", SysTime.fromUnixTime(attr.date));
114     writeln("Type: ", attr.type == DirType.NSPIRE_DIR ? "directory" : "file");
115     
116     return 0;
117 }
118 
119 int cp(NSpire nspire, DeviceInfo deviceInfo, string[] args) {
120     if (args.length < 2) {
121         help();
122         return 1;
123     }
124     
125     if (!checkExtFile(deviceInfo, args[0], args[1])) {
126         return ErrorCodes.NSPIRE_ERR_INVALID;
127     }
128     
129     auto error = nspire.copyFile(args[0], args[1]);
130     
131     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
132         writeln("Error: ", NSpire.errorStr(error));
133         return error;
134     }
135     
136     return 0;
137 }
138 
139 int mv(NSpire nspire, DeviceInfo deviceInfo, string[] args) {
140     import std.algorithm.searching : endsWith;
141     
142     if (args.length < 2) {
143         help();
144         return 1;
145     }
146     
147     if (!checkExtFile(deviceInfo, args[0], args[1])) {
148         return ErrorCodes.NSPIRE_ERR_INVALID;
149     }
150     
151     auto error = nspire.moveFile(args[0], args[1]);
152     
153     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
154         writeln("Error: ", NSpire.errorStr(error));
155         return error;
156     }
157     
158     return 0;
159 }
160 
161 int rm(NSpire nspire, DeviceInfo deviceInfo, string[] args) {
162     import std.algorithm.searching : endsWith;
163     
164     if (args.length < 1) {
165         help();
166         return 1;
167     }
168     
169     if (!checkExtFile(deviceInfo, args[0])) {
170         return ErrorCodes.NSPIRE_ERR_INVALID;
171     }
172     
173     auto error = nspire.deleteFile(args[0]);
174     
175     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
176         writeln("Error: ", NSpire.errorStr(error));
177         return error;
178     }
179     
180     return 0;
181 }
182 
183 int mkdir(NSpire nspire, string[] args) {
184     if (args.length < 1) {
185         help();
186         return 1;
187     }
188     
189     auto error = nspire.createDirectory(args[0]);
190     
191     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
192         writeln("Error: ", NSpire.errorStr(error));
193         return error;
194     }
195     
196     return 0;
197 }
198 
199 int rmdir(NSpire nspire, string[] args) {
200     if (args.length < 1) {
201         help();
202         return 1;
203     }
204     
205     auto error = nspire.deleteDirectory(args[0]);
206     
207     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
208         writeln("Error: ", NSpire.errorStr(error));
209         return error;
210     }
211     
212     return 0;
213 }
214 
215 int push(NSpire nspire, DeviceInfo deviceInfo, string[] args) {
216     import std.algorithm.searching : endsWith;
217     import std.file : read, FileException;
218     
219     if (args.length < 2) {
220         help();
221         return 1;
222     }
223     
224     void[] fileBuf;
225     
226     if (!checkExtFile(deviceInfo, args[0], args[1])) {
227         return ErrorCodes.NSPIRE_ERR_INVALID;
228     }
229     
230     try {
231         fileBuf = read(args[0]);
232     } catch (FileException e) {
233         writeln("Error: failed file read on local side");
234         return ErrorCodes.NSPIRE_ERR_INVALID;
235     }
236     
237     auto error = nspire.writeFile(args[1], fileBuf);
238     
239     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
240         writeln("Error: ", NSpire.errorStr(error));
241         return error;
242     }
243     
244     return 0;
245 }
246 
247 int pull(NSpire nspire, DeviceInfo deviceInfo, string[] args) {
248     import std.algorithm.searching : endsWith;
249     import std.file : write, FileException;
250     
251     if (args.length < 2) {
252         help();
253         return 1;
254     }
255     
256     void[] fileBuf;
257     int error;
258     size_t readBytes;
259     
260     if (!checkExtFile(deviceInfo, args[0], args[1])) {
261         return ErrorCodes.NSPIRE_ERR_INVALID;
262     }
263 
264     auto attr = nspire.getAttr(args[0], error);
265     
266     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
267         writeln("Error: ", NSpire.errorStr(error));
268         return error;
269     }
270     
271     fileBuf = new void[attr.size];
272     
273     error = nspire.readFile(args[0], fileBuf, readBytes);
274     
275     if (error != ErrorCodes.NSPIRE_ERR_SUCCESS) {
276         writeln("Error: ", NSpire.errorStr(error));
277         return error;
278     }
279     
280     if (readBytes < attr.size) {
281         writeln("033[1mWarning: the received file is smaller than expected, it may be corrupt\033[22m");
282     }
283     
284     if (readBytes > attr.size) {
285         writeln("Error: ", NSpire.errorStr(ErrorCodes.NSPIRE_ERR_NOMEM));
286         return ErrorCodes.NSPIRE_ERR_NOMEM;
287     }
288     
289     try {
290         write(args[1], fileBuf);
291     } catch (FileException e) {
292         writeln("Error: failed file write on local side");
293         return ErrorCodes.NSPIRE_ERR_INVALID;
294     }
295     
296     return 0;
297 }
298 
299 void help() {
300     writeln("NSpire Tools - filemanager\n");
301     writeln("Usage: nspire-tools filemanager [function [arguments]...]");
302     writeln("\tor: nspire-filemanager [function [arguments]...]...\n");
303     writeln("Currently defined functions:");
304     writeln("\tls (default /)");
305     writeln("\tls <path>");
306     writeln("\tinfo <path>");
307     writeln("\tcp <source path> <destination path>");
308     writeln("\tmv <source path> <destination path>");
309     writeln("\trm <path>");
310     writeln("\tmkdir <path>");
311     writeln("\trmdir <path>");
312     writeln("\tpush <local path> <remote path>");
313     writeln("\tpull <remote path> <local path>");
314 }
315 
316 bool checkExtFile(DeviceInfo deviceInfo, string[] paths...) {
317     import std.string : fromStringz;
318     return checkExt(deviceInfo.fileExtensions.file.ptr.fromStringz.idup, paths);
319 }
320 
321 bool checkExtOs(DeviceInfo deviceInfo, string[] paths...) {
322     import std.string : fromStringz;
323     return checkExt(deviceInfo.fileExtensions.os.ptr.fromStringz.idup, paths);
324 }
325 
326 private bool checkExt(string ext, string[] paths...) {
327     foreach (path; paths) {
328         if (path.length < 4 || path[$-ext.length..$] != ext) {
329             writeln("Error: files must have an ", ext, " extension");
330             return false;
331         }
332     }
333     
334     return true;
335 }