CLI interface to medialist (fossil mirror)
Révision | 1e158176c27abba819f2a3ccc384f80f526079d4 (tree) |
---|---|
l'heure | 2022-01-10 15:09:22 |
Auteur | mio <stigma@disr...> |
Commiter | mio |
add an implementation for the MLCommand.delete_ command. This also changes the MediaList structure to use public members instead of package.
FossilOrigin-Name: 873d7974aad4307a8b86764ff3ae7b5e6439173eca595b54838a9c8a6b0c79c5
@@ -18,25 +18,37 @@ | ||
18 | 18 | */ |
19 | 19 | module medialist; |
20 | 20 | |
21 | +import std.algorithm.sorting; | |
22 | +import std.conv; | |
21 | 23 | import std.datetime.date; |
22 | 24 | import std.datetime.systime; |
23 | 25 | import std.file; |
26 | +import std.path; | |
24 | 27 | import std.stdio; |
25 | 28 | import std.string; |
26 | 29 | |
27 | 30 | struct MediaList |
28 | 31 | { |
29 | -package: | |
30 | - string filePath; | |
32 | + immutable string filePath; | |
33 | + immutable string listName; | |
31 | 34 | bool isOpen = false; |
32 | 35 | } |
33 | 36 | |
34 | 37 | enum MLCommand |
35 | 38 | { |
36 | 39 | /** |
37 | - * Args: ["Item Name", "(Optional) Progress", "(Optional) Status". | |
40 | + * Add a new item to a list. | |
41 | + * | |
42 | + * Args: ["Item Name", "(Optional) Progress", "(Optional) Status"] | |
38 | 43 | */ |
39 | 44 | add, |
45 | + /** | |
46 | + * Delete items from a list. | |
47 | + * | |
48 | + * If no "Item ID"s are provided, this will delete the entire list. | |
49 | + * | |
50 | + * Args: ["(Optional) Item ID", ...(repeat for the amount of ids needed)] | |
51 | + */ | |
40 | 52 | delete_, |
41 | 53 | update, |
42 | 54 | } |
@@ -51,7 +63,8 @@ enum MLError | ||
51 | 63 | |
52 | 64 | MediaList* ml_open_list(string filePath) |
53 | 65 | { |
54 | - MediaList* ml = new MediaList(filePath); | |
66 | + string listName = stripExtension(baseName(filePath)); | |
67 | + MediaList* ml = new MediaList(filePath, listName); | |
55 | 68 | |
56 | 69 | if (false == exists(filePath)) { |
57 | 70 | File f = File(filePath, "w+"); |
@@ -96,6 +109,7 @@ MLError ml_send_command(MediaList* list, MLCommand command, string[] args) | ||
96 | 109 | res = _ml_add(list, args); |
97 | 110 | break; |
98 | 111 | case MLCommand.delete_: |
112 | + res = _ml_delete(list, args); | |
99 | 113 | break; |
100 | 114 | case MLCommand.update: |
101 | 115 | break; |
@@ -167,6 +181,105 @@ private MLError _ml_add(MediaList* list, string[] args) | ||
167 | 181 | return MLError.success; |
168 | 182 | } |
169 | 183 | |
184 | +/** | |
185 | + * Convert and sort an array of strings to "size_t". | |
186 | + * | |
187 | + * This assumes all elements in the array can be converted. If any fail, | |
188 | + * then "null" is returned from the function. | |
189 | + */ | |
190 | +private size_t[] _ml_conv_sort_num_list(const string[] args) | |
191 | +{ | |
192 | + size_t[] ids = new size_t[args.length]; | |
193 | + | |
194 | + foreach(size_t idx, const ref string arg; args) { | |
195 | + try { | |
196 | + ids[idx] = to!size_t(arg); | |
197 | + } catch (Exception e) { | |
198 | + return null; | |
199 | + } | |
200 | + } | |
201 | + | |
202 | + sort(ids); | |
203 | + | |
204 | + return ids; | |
205 | +} | |
206 | + | |
207 | +private MLError _ml_delete(MediaList* list, string[] args) | |
208 | +{ | |
209 | + if (true == list.isOpen) | |
210 | + return MLError.fileAlreadyOpen; | |
211 | + | |
212 | + if (0 == args.length) { | |
213 | + remove(list.filePath); | |
214 | + return MLError.success; | |
215 | + } | |
216 | + | |
217 | + size_t[] ids = _ml_conv_sort_num_list(args); | |
218 | + | |
219 | + if (null is ids) | |
220 | + return MLError.invalidArgs; | |
221 | + | |
222 | + File listFile = File(list.filePath); | |
223 | + list.isOpen = true; | |
224 | + | |
225 | + /* | |
226 | + * To avoid storing all lines in memory, we create a temporary file with | |
227 | + * which we write all lines that we are keeping. Once we're done writing, | |
228 | + * we then overwrite the actual list file with the contents of the temporary | |
229 | + * file. | |
230 | + * | |
231 | + * If someone knows of a better way to go about this, I wouldn't mind | |
232 | + * knowing. | |
233 | + */ | |
234 | + string tempFilePath = buildPath(tempDir(), | |
235 | + "temp" ~ baseName(list.filePath)); | |
236 | + File tempFile = File(tempFilePath, "w+"); | |
237 | + | |
238 | + size_t currentID = 1; | |
239 | + size_t idsIndex = 0; | |
240 | + string line; | |
241 | + bool pastHeader = false; | |
242 | + | |
243 | + while ((line = listFile.readln()) !is null) { | |
244 | + if (line[0] == '#') { | |
245 | + tempFile.write(line); | |
246 | + continue; | |
247 | + } | |
248 | + | |
249 | + if (false == pastHeader) { | |
250 | + tempFile.write(line); | |
251 | + pastHeader = true; | |
252 | + continue; | |
253 | + } | |
254 | + | |
255 | + if (ids[idsIndex] != currentID) | |
256 | + tempFile.write(line); | |
257 | + else | |
258 | + idsIndex += 1; | |
259 | + | |
260 | + currentID += 1; | |
261 | + } | |
262 | + | |
263 | + listFile.close(); | |
264 | + tempFile.close(); | |
265 | + | |
266 | + listFile = File(list.filePath, "w+"); | |
267 | + tempFile = File(tempFilePath); | |
268 | + | |
269 | + while ((line = tempFile.readln) !is null) { | |
270 | + listFile.write(line); | |
271 | + } | |
272 | + | |
273 | + listFile.close(); | |
274 | + tempFile.close(); | |
275 | + | |
276 | + remove(tempFilePath); | |
277 | + | |
278 | + list.isOpen = false; | |
279 | + | |
280 | + return MLError.success; | |
281 | +} | |
282 | + | |
170 | 283 | private enum MLHeaders |
171 | 284 | { |
172 | 285 | title = 0, |
@@ -294,7 +407,6 @@ void main() | ||
294 | 407 | @("Create a new list") |
295 | 408 | unittest |
296 | 409 | { |
297 | - import std.conv : to; | |
298 | 410 | import std.path : buildPath; |
299 | 411 | |
300 | 412 | const listPath = buildPath(tempDir(), "unittest1.tsv"); |