• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Freegle / iznik-server / #2569

12 Jan 2026 06:33AM UTC coverage: 88.018% (-0.007%) from 88.025%
#2569

push

edwh
Fix illustration scripts getting stuck in infinite loop on failing items

Changes:
- fetchBatch() now returns partial results instead of FALSE when individual
  items fail to return data (only true rate-limiting fails the batch)
- Add file-based tracking for items that repeatedly fail (/tmp/pollinations_failed.json)
- Items that fail 3 times are skipped for 1 day before retrying
- Both messages_illustrations.php and jobs_illustrations.php updated to:
  - Skip items that have exceeded failure threshold
  - Record failures for items that don't return data
  - Process successful results from partial batches

This prevents a single problematic item (like "2 toilet seat trainers") from
blocking all illustration generation indefinitely.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

0 of 47 new or added lines in 1 file covered. (0.0%)

112 existing lines in 3 files now uncovered.

26306 of 29887 relevant lines covered (88.02%)

31.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/include/misc/Tus.php
1
<?php
2

3
namespace Freegle\Iznik;
4

5
class Tus {
6
    public static function upload($url, $mime = "image/webp", $data = NULL) {
7
        # Basic uploader, which we use for migration.
8
        #error_log("Upload to " . TUS_UPLOADER . " url $url data len " . ($data ? strlen($data) : 0) . " mime $mime");
9
        $data = $data ? $data : file_get_contents($url);
21✔
10

11
        $url = TUS_UPLOADER;
21✔
12
        $chkAlgo = "crc32";
21✔
13
        $fileChk = base64_encode(hash($chkAlgo, $data, TRUE));
21✔
14
        $fileLen = strlen($data);
21✔
15

16
        $ch = curl_init();
21✔
17
        curl_setopt($ch, CURLOPT_URL, $url);
21✔
18
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
21✔
19
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
21✔
20
        curl_setopt($ch, CURLOPT_NOBODY, TRUE);
21✔
21
        curl_setopt($ch, CURLOPT_HEADER, TRUE);
21✔
22
        $headers = [
21✔
23
            "Tus-Resumable" => "1.0.0",
21✔
24
            "Content-Type" => "application/offset+octet-stream",
21✔
25
            "Upload-Length" => $fileLen,
21✔
26
            "Upload-Metadata" => "relativePath bnVsbA==,name " . base64_encode($mime) . ",type " . base64_encode("image/webp") . ",filetype " . base64_encode("image/webp") . ",filename " . base64_encode('image.webp')
21✔
27
        ];
21✔
28
        $fheaders = [];
21✔
29
        foreach ($headers as $key => $value) {
21✔
30
            $fheaders[] = $key . ": " . $value;
21✔
31
        }
32
        curl_setopt($ch, CURLOPT_HTTPHEADER, $fheaders);
21✔
33

34
        $result = curl_exec($ch);
21✔
35
        $errno = curl_errno($ch);
21✔
36
        if ($errno) {
21✔
37
            error_log("POST error: $errno " . curl_error($ch));
×
38
            return NULL;
×
39
        }
40

41
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 201) {
21✔
42
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
21✔
43
            $header = substr($result, 0, $headerSize);
21✔
44
            $headers = Tus::getHeaders($header);
21✔
45
            #error_log(json_encode($headers));
46
            $url = $headers['location'];
21✔
47

48
            if (!$url) {
21✔
49
                return NULL;
21✔
50
            }
51
            #error_log("Post returned location $url");
52
        } else {
53
            #error_log("Error creating file");
UNCOV
54
            return NULL;
×
55
        }
56
        curl_close($ch);
21✔
57

58
        // Get file offset/size on the server
59
        $ch = curl_init();
21✔
60
        curl_setopt($ch, CURLOPT_URL, $url);
21✔
61
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD");
21✔
62
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
21✔
63
        curl_setopt($ch, CURLOPT_NOBODY, TRUE);
21✔
64
        curl_setopt($ch, CURLOPT_HEADER, TRUE);
21✔
65
        $headers = [
21✔
66
            "Tus-Resumable" => "1.0.0",
21✔
67
            "Content-Type" => "application/offset+octet-stream",
21✔
68
        ];
21✔
69
        $fheaders = [];
21✔
70
        foreach ($headers as $key => $value) {
21✔
71
            $fheaders[] = $key . ": " . $value;
21✔
72
        }
73
        curl_setopt($ch, CURLOPT_HTTPHEADER, $fheaders);
21✔
74

75
        $result = curl_exec($ch);
21✔
76
        $errno = curl_errno($ch);
21✔
77
        if (curl_errno($ch)) {
21✔
78
            error_log("Get size on $url error $errno: " . curl_error($ch));
×
79
            return NULL;
×
80
        }
81

82
        $headers = explode("\r\n", $result);
21✔
83
        $headers = array_map(function ($item) {
21✔
84
            return explode(": ", $item);
21✔
85
        }, $headers);
21✔
86
        $header = array_filter($headers, function ($value) {
21✔
87
            if ($value[0] === "Upload-Offset") {
21✔
88
                return TRUE;
×
89
            }
90
        });
21✔
91

92
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
21✔
93
        } else if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 404) {
×
94
            error_log("File not found! $url");
×
95
            return NULL;
×
96
        }
97
        curl_close($ch);
21✔
98

99
        // Upload whole file
100
        $ch = curl_init();
21✔
101
        curl_setopt($ch, CURLOPT_URL, $url);
21✔
102
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
21✔
103
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
21✔
104
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
21✔
105
        curl_setopt($ch, CURLOPT_HEADER, TRUE);
21✔
106
        $headers = [
21✔
107
            "Content-Type" => "application/offset+octet-stream",
21✔
108
            "Tus-Resumable" => "1.0.0",
21✔
109
            "Upload-Offset" => 0,
21✔
110
            "Upload-Checksum" => "$chkAlgo $fileChk",
21✔
111
        ];
21✔
112
        $fheaders = [];
21✔
113
        foreach ($headers as $key => $value) {
21✔
114
            $fheaders[] = $key . ": " . $value;
21✔
115
        }
116
        curl_setopt($ch, CURLOPT_HTTPHEADER, $fheaders);
21✔
117

118
        $result = curl_exec($ch);
21✔
119
        if (curl_errno($ch)) {
21✔
120
            error_log("Full upload error: " . curl_error($ch));
×
121
        }
122
        $rc = curl_getinfo($ch, CURLINFO_HTTP_CODE);
21✔
123
        curl_close($ch);
21✔
124
        #error_log("Upload rc $rc");
125

126
        if  ($rc == 200 || $rc == 204) {
21✔
127
            return $url;
21✔
128
        } else {
129
            return NULL;
×
130
        }
131
    }
132

133
    public static function getHeaders($respHeaders) {
134
        $headers = array();
21✔
135

136
        $headerText = substr($respHeaders, 0, strpos($respHeaders, "\r\n\r\n"));
21✔
137

138
        foreach (explode("\r\n", $headerText) as $i => $line) {
21✔
139
            if ($i === 0) {
21✔
140
                $headers['http_code'] = $line;
21✔
141
            } else {
142
                list ($key, $value) = explode(': ', $line);
21✔
143

144
                $headers[strtolower($key)] = $value;
21✔
145
            }
146
        }
147

148
        return $headers;
21✔
149
    }
150
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc