D wrapper around (some) of the pixiv web API
Revision | ab3f05ebdbae3ed2731ae9221fbc72a4f9c977df (tree) |
---|---|
Time | 2023-08-13 12:03:36 |
Author | supercell <stigma@disr...> |
Commiter | supercell |
Improve partial download support
Should now work for multi-paged illustrations and manga.
@@ -1,8 +1,7 @@ | ||
1 | 1 | module pixivd.client; |
2 | 2 | |
3 | -import core.sync.mutex : Mutex; | |
4 | - | |
5 | 3 | import std.array : appender; |
4 | +import std.file : FileException, rename; | |
6 | 5 | import std.format : format; |
7 | 6 | import std.json; |
8 | 7 | import std.net.curl : HTTP; |
@@ -721,33 +720,22 @@ private: | ||
721 | 720 | { |
722 | 721 | import std.datetime.systime; |
723 | 722 | |
724 | - import std.file : FileException, exists, getSize, rename, setTimes; | |
723 | + import std.file : exists, rename, setTimes; | |
725 | 724 | import std.path : extension; |
726 | 725 | import std.stdio : File; |
727 | 726 | |
728 | 727 | const baseFileName = illust.id ~ illust.urls["original"].extension; |
729 | 728 | const partFileName = baseFileName ~ ".part"; |
729 | + string mode = "w+"; | |
730 | 730 | |
731 | 731 | if (true == exists(baseFileName) && false == overwrite) |
732 | 732 | { |
733 | 733 | throw new FileException(baseFileName, "File already exists"); |
734 | 734 | } |
735 | - | |
736 | - string mode = "w+"; | |
737 | - | |
738 | - if (exists(partFileName)) { | |
739 | - // Resume previous attempt at downloading. | |
740 | - m_client.url = illust.urls["original"]; | |
741 | - m_client.method = HTTP.Method.head; | |
742 | - m_client.perform(); | |
743 | - if ("accept-ranges" in m_client.responseHeaders()) { | |
744 | - ulong bytesRead = getSize(partFileName); | |
745 | - m_client.addRequestHeader("Range", format!"bytes=%d-"(bytesRead)); | |
746 | - mode = "a+"; | |
747 | - } | |
748 | - // If the "accept-ranges" header was not present, | |
749 | - // we'll restart the entire download. | |
750 | - m_client.method = HTTP.Method.get; | |
735 | + if (true == exists(partFileName)) { | |
736 | + this._setupPartialDownload(illust.urls["original"], partFileName); | |
737 | + mode = "a+"; | |
738 | + this._resetHeaders(); | |
751 | 739 | } |
752 | 740 | |
753 | 741 | File imageFile = File(partFileName, mode); |
@@ -813,7 +801,17 @@ private: | ||
813 | 801 | import std.path : baseName, stripExtension; |
814 | 802 | |
815 | 803 | const url = jsonobj["urls"]["original"].str; |
816 | - File img = File(baseName(url), "w+"); | |
804 | + const baseFileName = baseName(url); | |
805 | + const partFileName = baseFileName ~ ".part"; | |
806 | + string mode = "w+"; | |
807 | + | |
808 | + if (true == exists(partFileName)) { | |
809 | + this._setupPartialDownload(jsonobj["urls"]["original"].str, partFileName); | |
810 | + mode = "a+"; | |
811 | + this._resetHeaders(); | |
812 | + } | |
813 | + | |
814 | + File img = File(partFileName, mode); | |
817 | 815 | |
818 | 816 | m_client.url = url; |
819 | 817 | m_client.onReceive = (ubyte[] data) { |
@@ -828,6 +826,7 @@ private: | ||
828 | 826 | |
829 | 827 | m_client.perform(); |
830 | 828 | img.close(); |
829 | + rename(partFileName, baseFileName); | |
831 | 830 | emit(DownloadCompleteEvent()); |
832 | 831 | } |
833 | 832 | } |
@@ -870,11 +869,20 @@ private: | ||
870 | 869 | auto bodyArr = json["body"].array; |
871 | 870 | |
872 | 871 | foreach(obj; bodyArr) { |
873 | - auto filename = baseName(obj["urls"]["original"].str); | |
874 | - if (true == exists(filename) && false == overwrite) { | |
875 | - throw new FileException(filename, "File already exists"); | |
872 | + const baseFileName = baseName(obj["urls"]["original"].str); | |
873 | + const partFileName = baseFileName ~ ".part"; | |
874 | + string mode = "w+"; | |
875 | + | |
876 | + if (true == exists(baseFileName) && false == overwrite) { | |
877 | + throw new FileException(baseFileName, "File already exists"); | |
878 | + } | |
879 | + if (true == exists(partFileName)) { | |
880 | + this._setupPartialDownload(obj["urls"]["original"].str, partFileName); | |
881 | + mode = "a+"; | |
882 | + this._resetHeaders(); | |
876 | 883 | } |
877 | - File outFile = File(filename, "w+"); | |
884 | + | |
885 | + File outFile = File(partFileName, mode); | |
878 | 886 | m_client.url = obj["urls"]["original"].str; |
879 | 887 | m_client.onReceive = (ubyte[] data) { |
880 | 888 | outFile.rawWrite(data); |
@@ -886,11 +894,12 @@ private: | ||
886 | 894 | }; |
887 | 895 | m_client.perform(); |
888 | 896 | outFile.close(); |
897 | + rename(partFileName, baseFileName); | |
889 | 898 | emit(DownloadCompleteEvent()); |
890 | 899 | |
891 | 900 | SysTime createDate = SysTime.fromISOExtString(illust.createDate); |
892 | 901 | |
893 | - setTimes(filename, createDate, createDate); | |
902 | + setTimes(baseFileName, createDate, createDate); | |
894 | 903 | } |
895 | 904 | } |
896 | 905 |
@@ -1059,4 +1068,25 @@ private: | ||
1059 | 1068 | m_client.setCookie("PHPSESSID=" ~ m_phpsessid); |
1060 | 1069 | } |
1061 | 1070 | } |
1071 | + | |
1072 | + /// | |
1073 | + /// Setup m_client for resuming a previous download. | |
1074 | + /// | |
1075 | + /// If the particular request doesn't support partial | |
1076 | + /// downloads | |
1077 | + void _setupPartialDownload(string url, string filename) | |
1078 | + { | |
1079 | + import std.file : getSize; | |
1080 | + | |
1081 | + m_client.url = url; | |
1082 | + m_client.method = HTTP.Method.head; | |
1083 | + m_client.perform(); | |
1084 | + if ("accept-ranges" in m_client.responseHeaders()) { | |
1085 | + ulong bytesRead = getSize(filename); | |
1086 | + m_client.addRequestHeader("Range", format!"bytes=%d-"(bytesRead)); | |
1087 | + } | |
1088 | + // If the "accept-ranges" header was not present, | |
1089 | + // we'll restart the entire download. | |
1090 | + m_client.method = HTTP.Method.get; | |
1091 | + } | |
1062 | 1092 | } |