• R/O
  • HTTP
  • SSH
  • HTTPS

提交

标签
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

CLI interface to medialist (fossil mirror)


Commit MetaInfo

修订版d2ebf9357a59a42265e146622ddc98aae711acca (tree)
时间2022-01-20 14:45:53
作者mio <stigma@disr...>
Commitermio

Log Message

add an implementation for "update" command.
also changes the MediaListItem structure a little.

FossilOrigin-Name: 71855bed5d0d00b82f77be0aea3dacf6e66506778b4c80df5117499840f6734c

更改概述

差异

--- a/medialist.d
+++ b/medialist.d
@@ -40,10 +40,9 @@ struct MediaListItem
4040 string title;
4141 string progress;
4242 string status;
43- /* start_date */
44- /* end_date */
45- /* last_updated */
46- bool isValid;
43+ string startDate;
44+ string endDate;
45+ string lastUpdated;
4746 }
4847
4948 struct MediaListHeader
@@ -69,6 +68,18 @@ enum MLCommand
6968 * Args: ["(Optional) Item ID", ...(repeat for the amount of ids needed)]
7069 */
7170 delete_,
71+ /**
72+ * Update an item on a list.
73+ *
74+ * Args: ["Item ID", "field::value", "field::value", ...]
75+ *
76+ * For example: ml_send_command(list, MLCommand.update, ["1", "status:READING"]);
77+ * The "field" is automatically converted to lowercase, while the value is kept
78+ * as-is.
79+ *
80+ * To update the start and end date of an item, use "start_date" and
81+ * "end_date" respectively.
82+ */
7283 update,
7384 }
7485
@@ -212,6 +223,9 @@ MLError ml_fetch_item(MediaList* list, size_t id, MediaListItem* item)
212223 size_t titleIndex = 0;
213224 size_t progressIndex = 0;
214225 size_t statusIndex = 0;
226+ size_t startIndex = 0;
227+ size_t endIndex = 0;
228+ size_t lastIndex = 0;
215229
216230 foreach(size_t idx, int[2] header; headerPositions) {
217231 switch(header[0]) {
@@ -224,13 +238,23 @@ MLError ml_fetch_item(MediaList* list, size_t id, MediaListItem* item)
224238 case MLHeaders.status:
225239 statusIndex = header[1];
226240 break;
241+ case MLHeaders.startDate:
242+ startIndex = header[1];
243+ break;
244+ case MLHeaders.endDate:
245+ endIndex = header[1];
246+ break;
247+ case MLHeaders.lastUpdated:
248+ lastIndex = header[1];
249+ break;
227250 default:
228251 break;
229252 }
230253 }
231254
232255 MediaListItem newItem = MediaListItem(sections[titleIndex],
233- sections[progressIndex], sections[statusIndex], true);
256+ sections[progressIndex], sections[statusIndex], sections[startIndex],
257+ sections[endIndex], sections[lastIndex]);
234258
235259 *item = newItem;
236260
@@ -252,12 +276,15 @@ MediaListItem[] ml_fetch_items(MediaList* list, size_t[] ids ...)
252276 bool pastHeader = false;
253277 MediaListItem[] items;
254278
279+ int[2][6] headerPositions = _ml_get_header_positions(list);
255280 size_t titleIndex = 0;
256281 size_t progressIndex = 0;
257282 size_t statusIndex = 0;
258- int[2][6] headerPositions = _ml_get_header_positions(list);
283+ size_t startIndex = 0;
284+ size_t endIndex = 0;
285+ size_t lastIndex = 0;
259286
260- foreach(ref header; headerPositions) {
287+ foreach(size_t idx, int[2] header; headerPositions) {
261288 switch(header[0]) {
262289 case MLHeaders.title:
263290 titleIndex = header[1];
@@ -268,6 +295,15 @@ MediaListItem[] ml_fetch_items(MediaList* list, size_t[] ids ...)
268295 case MLHeaders.status:
269296 statusIndex = header[1];
270297 break;
298+ case MLHeaders.startDate:
299+ startIndex = header[1];
300+ break;
301+ case MLHeaders.endDate:
302+ endIndex = header[1];
303+ break;
304+ case MLHeaders.lastUpdated:
305+ lastIndex = header[1];
306+ break;
271307 default:
272308 break;
273309 }
@@ -287,8 +323,9 @@ MediaListItem[] ml_fetch_items(MediaList* list, size_t[] ids ...)
287323
288324 if (true == canFind(ids, currentID)) {
289325 string[] sections = line.strip().split("\t");
290- items ~= MediaListItem(sections[titleIndex], sections[progressIndex],
291- sections[statusIndex], true);
326+ items ~= MediaListItem(sections[titleIndex],
327+ sections[progressIndex], sections[statusIndex], sections[startIndex],
328+ sections[endIndex], sections[lastIndex]);
292329 }
293330
294331 currentID += 1;
@@ -313,9 +350,12 @@ MediaListItem[] ml_fetch_all(MediaList* list)
313350 size_t titleIndex = 0;
314351 size_t progressIndex = 0;
315352 size_t statusIndex = 0;
353+ size_t startIndex = 0;
354+ size_t endIndex = 0;
355+ size_t lastIndex = 0;
316356
317- foreach(ref header; headerPositions) {
318- switch (header[0]) {
357+ foreach(size_t idx, int[2] header; headerPositions) {
358+ switch(header[0]) {
319359 case MLHeaders.title:
320360 titleIndex = header[1];
321361 break;
@@ -325,6 +365,15 @@ MediaListItem[] ml_fetch_all(MediaList* list)
325365 case MLHeaders.status:
326366 statusIndex = header[1];
327367 break;
368+ case MLHeaders.startDate:
369+ startIndex = header[1];
370+ break;
371+ case MLHeaders.endDate:
372+ endIndex = header[1];
373+ break;
374+ case MLHeaders.lastUpdated:
375+ lastIndex = header[1];
376+ break;
328377 default:
329378 break;
330379 }
@@ -344,8 +393,9 @@ MediaListItem[] ml_fetch_all(MediaList* list)
344393
345394 string[] sections = line.strip().split("\t");
346395
347- items ~= MediaListItem(sections[titleIndex], sections[progressIndex],
348- sections[statusIndex], true);
396+ items ~= MediaListItem(sections[titleIndex],
397+ sections[progressIndex], sections[statusIndex], sections[startIndex],
398+ sections[endIndex], sections[lastIndex]);
349399 }
350400
351401 list.isOpen = false;
@@ -366,6 +416,7 @@ MLError ml_send_command(MediaList* list, MLCommand command, string[] args)
366416 res = _ml_delete(list, args);
367417 break;
368418 case MLCommand.update:
419+ res = _ml_update(list, args);
369420 break;
370421 default:
371422 break;
@@ -534,6 +585,182 @@ private MLError _ml_delete(MediaList* list, string[] args)
534585 return MLError.success;
535586 }
536587
588+private MLError _ml_update(MediaList* list, string[] args)
589+{
590+ if (list.isOpen)
591+ return MLError.fileAlreadyOpen;
592+
593+ if (2 > args.length)
594+ return MLError.invalidArgs;
595+
596+ string title = null;
597+ string progress = null;
598+ string status = null;
599+ string startDate = "";
600+ string endDate = "";
601+
602+ size_t id;
603+
604+ try {
605+ id = to!size_t(args[0]);
606+ } catch (Exception e) {
607+ return MLError.invalidArgs;
608+ }
609+
610+ foreach(string arg; args) {
611+ string[] kv = arg.split("::");
612+
613+ if (2 > kv.length)
614+ continue;
615+
616+ string k = kv[0].toLower();
617+ string v = kv[1];
618+
619+ switch (k) {
620+ case "title":
621+ title = v;
622+ break;
623+ case "status":
624+ status = v;
625+ break;
626+ case "progress":
627+ progress = v;
628+ break;
629+ case "start_date":
630+ startDate = v;
631+ break;
632+ case "end_date":
633+ endDate = v;
634+ break;
635+ default:
636+ break;
637+ }
638+ }
639+
640+ string tempFilePath = buildPath(tempDir, "ml_temp.tsv");
641+ File tempFile = File(tempFilePath, "w+");
642+ scope(exit) remove(tempFilePath);
643+
644+ int[2][6] headerPositions = _ml_get_header_positions(list);
645+ size_t titleIndex = 0;
646+ size_t progressIndex = 0;
647+ size_t statusIndex = 0;
648+ size_t startDateIndex = 0;
649+ size_t endDateIndex = 0;
650+ size_t lastUpdatedIndex = 0;
651+
652+ foreach (ref header; headerPositions) {
653+ switch (header[0]) {
654+ case MLHeaders.title:
655+ titleIndex = header[1];
656+ break;
657+ case MLHeaders.progress:
658+ progressIndex = header[1];
659+ break;
660+ case MLHeaders.status:
661+ statusIndex = header[1];
662+ break;
663+ case MLHeaders.startDate:
664+ startDateIndex = header[1];
665+ break;
666+ case MLHeaders.endDate:
667+ endDateIndex = header[1];
668+ break;
669+ case MLHeaders.lastUpdated:
670+ lastUpdatedIndex = header[1];
671+ break;
672+ default:
673+ break;
674+ }
675+ }
676+
677+ File listFile = File(list.filePath, "r");
678+ list.isOpen = true;
679+
680+ string line;
681+ bool pastHeader;
682+ size_t currentIndex = 1;
683+
684+ while ((line = listFile.readln()) !is null) {
685+ if (line.length == 0)
686+ continue;
687+
688+ if (line[0] == '#') {
689+ tempFile.write(line);
690+ continue;
691+ }
692+
693+ if (false == pastHeader) {
694+ pastHeader = true;
695+ tempFile.write(line);
696+ continue;
697+ }
698+
699+ if (currentIndex == id) {
700+ string[] sections = line.strip().split("\t");
701+ if (title !is null)
702+ sections[titleIndex] = title;
703+ if (progress !is null)
704+ sections[progressIndex] = progress;
705+
706+ if (status !is null) {
707+ string oldStatus = sections[statusIndex];
708+
709+ if ((oldStatus.toLower == "plan-to-read" && status == "reading") ||
710+ (oldStatus.toLower == "plan-to-watch" && status == "watching")) {
711+
712+ if (startDate == "") {
713+ Date date = cast(Date)Clock.currTime;
714+ startDate = format!"%d-%02d-%02d"(date.year, date.month, date.day);
715+ sections[startDateIndex] = startDate;
716+ }
717+ }
718+
719+ if ((oldStatus.toLower == "reading" && status == "complete") ||
720+ (oldStatus.toLower == "watching" && status == "complete")) {
721+
722+ if (endDate == "") {
723+ Date date = cast(Date)Clock.currTime;
724+ endDate = format!"%d-%02d-%02d"(date.year, date.month, date.day);
725+ sections[endDateIndex] = endDate;
726+ }
727+ }
728+
729+ sections[statusIndex] = status;
730+ }
731+
732+ if (startDate !is null)
733+ sections[startDateIndex] = startDate;
734+ if (endDate !is null)
735+ sections[endDateIndex] = endDate;
736+
737+ Date date = cast(Date)Clock.currTime();
738+ sections[lastUpdatedIndex] = format!"%d-%02d-%02d"(date.year, date.month, date.day);
739+ tempFile.writeln(join(sections, "\t"));
740+ } else {
741+ tempFile.write(line);
742+ }
743+
744+ currentIndex += 1;
745+ }
746+
747+ listFile.close();
748+ tempFile.flush();
749+ tempFile.close();
750+
751+ tempFile = File(tempFilePath, "r");
752+ listFile = File(list.filePath, "w+");
753+
754+ while ((line = tempFile.readln()) !is null) {
755+ listFile.write(line);
756+ }
757+ listFile.flush();
758+
759+ list.isOpen = false;
760+
761+ return MLError.success;
762+}
763+
537764 private enum MLHeaders
538765 {
539766 title = 0,
@@ -639,6 +866,14 @@ public void runMediaListUnitTests()
639866 "Check header position variance and case-insensitivity.");
640867 ok(unittest_fetchItems124(),
641868 "Can fetch multiple items by ID (1, 4, and 2).");
869+ ok(unittest_updateItemTitle(),
870+ "Can update an item's title.");
871+ ok(unittest_updateItemStatus(),
872+ "Can update an item's status.");
873+ ok(unittest_updateItemProgress(),
874+ "Can update an item's progress.");
875+ ok(unittest_updateItemAll(),
876+ "Can update all aspects of an item.");
642877 }
643878
644879 private bool unittest_createNewList()
@@ -1152,3 +1387,217 @@ private bool unittest_fetchItems124()
11521387
11531388 return true;
11541389 }
1390+
1391+private bool unittest_updateItemTitle()
1392+{
1393+ enum listName = __FUNCTION__;
1394+ enum fileName = listName ~ ".tsv";
1395+
1396+ MediaList* list = ml_open_list(fileName);
1397+ if (list is null) {
1398+ diag("ml_open_list returned null.");
1399+ return false;
1400+ }
1401+
1402+ scope(exit) {
1403+ ml_send_command(list, MLCommand.delete_, []);
1404+ ml_free_list(list);
1405+ }
1406+
1407+ MLError res;
1408+
1409+ res = ml_send_command(list, MLCommand.add, ["Ietm 1"]);
1410+ if (MLError.success != res) {
1411+ diag("ml_send_command(list, add, [Ietm 1]) failed.");
1412+ return false;
1413+ }
1414+
1415+ res = ml_send_command(list, MLCommand.update, ["1", "title::Item 1"]);
1416+ if (MLError.success != res) {
1417+ diag("Failed to send UPDATE command [1, title::Item 1].");
1418+ return false;
1419+ }
1420+
1421+ MediaListItem item;
1422+ res = ml_fetch_item(list, 1, &item);
1423+ if (MLError.success != res) {
1424+ diag("Failed to fetch item 1 from list.");
1425+ return false;
1426+ }
1427+
1428+ if ("Item 1" != item.title) {
1429+ diag("Failed to update title from 'Ietm 1' to 'Item 1'.");
1430+ return false;
1431+ }
1432+
1433+ return true;
1434+}
1435+
1436+private bool unittest_updateItemStatus()
1437+{
1438+ enum listName = __FUNCTION__;
1439+ enum fileName = listName ~ ".tsv";
1440+
1441+ MediaList* list = ml_open_list(fileName);
1442+ if (list is null) {
1443+ diag("ml_open_list returned null.");
1444+ return false;
1445+ }
1446+
1447+ scope(exit) {
1448+ ml_send_command(list, MLCommand.delete_, []);
1449+ ml_free_list(list);
1450+ }
1451+
1452+ MLError res;
1453+
1454+ res = ml_send_command(list, MLCommand.add, ["Item 1", null, "PLAN-TO-READ"]);
1455+ if (MLError.success != res) {
1456+ diag("ml_send_command(list, add, [Item 1, null, PLAN-TO-READ]) failed.");
1457+ return false;
1458+ }
1459+
1460+ res = ml_send_command(list, MLCommand.update, ["1", "status::READING"]);
1461+ if (MLError.success != res) {
1462+ diag("Failed to send UPDATE command [1, status::READING].");
1463+ return false;
1464+ }
1465+
1466+ MediaListItem item;
1467+ res = ml_fetch_item(list, 1, &item);
1468+ if (MLError.success != res) {
1469+ diag("Failed to fetch item 1 from list.");
1470+ return false;
1471+ }
1472+
1473+ if ("READING" != item.status) {
1474+ diag("Failed to update status from 'PLAN-TO-READ' to 'READING'.");
1475+ return false;
1476+ }
1477+
1478+ return true;
1479+}
1480+
1481+private bool unittest_updateItemProgress()
1482+{
1483+ enum listName = __FUNCTION__;
1484+ enum fileName = listName ~ ".tsv";
1485+
1486+ MediaList* list = ml_open_list(fileName);
1487+ if (list is null) {
1488+ diag("ml_open_list returned null.");
1489+ return false;
1490+ }
1491+
1492+ scope(exit) {
1493+ ml_send_command(list, MLCommand.delete_, []);
1494+ ml_free_list(list);
1495+ }
1496+
1497+ MLError res;
1498+
1499+ res = ml_send_command(list, MLCommand.add, ["Item 1"]);
1500+ if (MLError.success != res) {
1501+ diag("ml_send_command(list, add, [Item 1]) failed.");
1502+ return false;
1503+ }
1504+
1505+ res = ml_send_command(list, MLCommand.update, ["1", "progress::10/10"]);
1506+ if (MLError.success != res) {
1507+ diag("Failed to send UPDATE command [1, progress::10/10].");
1508+ return false;
1509+ }
1510+
1511+ MediaListItem item;
1512+ res = ml_fetch_item(list, 1, &item);
1513+ if (MLError.success != res) {
1514+ diag("Failed to fetch item 1 from list.");
1515+ return false;
1516+ }
1517+
1518+ if ("10/10" != item.progress) {
1519+ diag("Failed to update progress from '-/-' to '10/10'.");
1520+ return false;
1521+ }
1522+
1523+ return true;
1524+}
1525+
1526+private bool unittest_updateItemAll()
1527+{
1528+ enum listName = __FUNCTION__;
1529+ enum fileName = listName ~ ".tsv";
1530+
1531+ MediaList* list = ml_open_list(fileName);
1532+ if (list is null) {
1533+ diag("ml_open_list returned null.");
1534+ return false;
1535+ }
1536+
1537+ scope(exit) {
1538+ ml_send_command(list, MLCommand.delete_, []);
1539+ ml_free_list(list);
1540+ }
1541+
1542+ MLError res;
1543+
1544+ res = ml_send_command(list, MLCommand.add, ["Ietm 1"]);
1545+ if (MLError.success != res) {
1546+ diag("ml_send_command(list, add, [Ietm 1]) failed.");
1547+ return false;
1548+ }
1549+
1550+ res = ml_send_command(list, MLCommand.update,
1551+ ["1",
1552+ "start_date::2021-02-16",
1553+ "end_date::2022-01-20",
1554+ "progress::60/100",
1555+ "status::READING",
1556+ "title::MediaList"]
1557+ );
1558+ if (MLError.success != res) {
1559+ diag("Failed to send UPDATE command.");
1560+ return false;
1561+ }
1562+
1563+ MediaListItem item;
1564+ res = ml_fetch_item(list, 1, &item);
1565+ if (MLError.success != res) {
1566+ diag("Failed to fetch item 1 from list.");
1567+ return false;
1568+ }
1569+
1570+ if ("MediaList" != item.title) {
1571+ diag("Failed to update item title from 'Ietm 1' to 'MediaList'.");
1572+ return false;
1573+ }
1574+
1575+ if ("60/100" != item.progress) {
1576+ diag("Failed to update progress from '-/-' to '60/100'.");
1577+ return false;
1578+ }
1579+
1580+ if ("READING" != item.status) {
1581+ diag("Failed to update status from 'UNKNOWN' to 'READING'.");
1582+ return false;
1583+ }
1584+
1585+ if ("2021-02-16" != item.startDate) {
1586+ diag("Failed to update start_date from '' to '2021-02-16'.");
1587+ return false;
1588+ }
1589+
1590+ if ("2022-01-20" != item.endDate) {
1591+ diag("Failed to update end_date from '' to '2022-01-20'.");
1592+ return false;
1593+ }
1594+
1595+ Date date = cast(Date)Clock.currTime;
1596+ string currentDate = format!"%d-%02d-%02d"(date.year, date.month, date.day);
1597+ if (currentDate != item.lastUpdated) {
1598+ diag("Failed to update last_update when running the UPDATE command.");
1599+ return false;
1600+ }
1601+
1602+ return true;
1603+}