Subversion Repositories SvarDOS

Rev

Rev 1022 | Rev 1267 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1022 Rev 1037
1
<?php /*
1
<?php /*
2
 
2
 
3
  SvarDOS repo index builder
3
  SvarDOS repo index builder
4
  Copyright (C) Mateusz Viste 2012-2022
4
  Copyright (C) Mateusz Viste 2012-2022
5
 
5
 
6
  buildidx computes an index json file for the SvarDOS repository.
6
  buildidx computes an index json file for the SvarDOS repository.
7
  it must be executed pointing to a directory that stores packages (*.svp)
7
  it must be executed pointing to a directory that stores packages (*.svp)
8
  files. buildidx will generate the index file and save it into the package
8
  files. buildidx will generate the index file and save it into the package
9
  repository.
9
  repository.
10
 
10
 
11
  requires php-zip
11
  requires php-zip
12
 
12
 
13
  28 feb 2022: svarcom allowed to have a COMMAND.COM file without subdirectory
13
  28 feb 2022: svarcom allowed to have a COMMAND.COM file without subdirectory
14
  24 feb 2022: added hardcoded hack to translate version 'x.xx' to '0.44' (NESticle)
14
  24 feb 2022: added hardcoded hack to translate version 'x.xx' to '0.44' (NESticle)
15
  23 feb 2022: basic validation of source archives (not empty + matches an existing svp file)
15
  23 feb 2022: basic validation of source archives (not empty + matches an existing svp file)
16
  21 feb 2022: buildidx collects categories looking at the dir layout of each package + improved version string parsing (replaced version_compare call by dos_version_compare)
16
  21 feb 2022: buildidx collects categories looking at the dir layout of each package + improved version string parsing (replaced version_compare call by dos_version_compare)
17
  17 feb 2022: checking for non-8+3 filenames in packages and duplicates + devload no longer part of CORE
17
  17 feb 2022: checking for non-8+3 filenames in packages and duplicates + devload no longer part of CORE
18
  16 feb 2022: added warning about overlong version strings and wild files location
18
  16 feb 2022: added warning about overlong version strings and wild files location
19
  15 feb 2022: index is generated as json, contains all filenames and alt versions
19
  15 feb 2022: index is generated as json, contains all filenames and alt versions
20
  14 feb 2022: packages are expected to have the *.svp extension
20
  14 feb 2022: packages are expected to have the *.svp extension
21
  12 feb 2022: skip source packages from being processed (*.src.zip)
21
  12 feb 2022: skip source packages from being processed (*.src.zip)
22
  20 jan 2022: rewritten the code from ANSI C to PHP for easier maintenance
22
  20 jan 2022: rewritten the code from ANSI C to PHP for easier maintenance
23
  13 feb 2021: 'title' LSM field is no longer looked after
23
  13 feb 2021: 'title' LSM field is no longer looked after
24
  11 feb 2021: lsm headers are no longer checked, so it is compatible with the simpler lsm format used by SvarDOS
24
  11 feb 2021: lsm headers are no longer checked, so it is compatible with the simpler lsm format used by SvarDOS
25
  13 jan 2021: removed the identification line, changed CRC32 to bsum, not creating the listing.txt file and stopped compressing index
25
  13 jan 2021: removed the identification line, changed CRC32 to bsum, not creating the listing.txt file and stopped compressing index
26
  23 apr 2017: uncompressed index is no longer created, added CRC32 of zib (bin only) files, if present
26
  23 apr 2017: uncompressed index is no longer created, added CRC32 of zib (bin only) files, if present
27
  28 aug 2016: listing.txt is always written inside the repo dir (instead of inside current dir)
27
  28 aug 2016: listing.txt is always written inside the repo dir (instead of inside current dir)
28
  27 aug 2016: accepting full paths to repos (starting with /...)
28
  27 aug 2016: accepting full paths to repos (starting with /...)
29
  07 dec 2013: rewritten buildidx in ANSI C89
29
  07 dec 2013: rewritten buildidx in ANSI C89
30
  19 aug 2013: add a compressed version of the index file to repos (index.gz)
30
  19 aug 2013: add a compressed version of the index file to repos (index.gz)
31
  22 jul 2013: creating a listing.txt file with list of packages
31
  22 jul 2013: creating a listing.txt file with list of packages
32
  18 jul 2013: writing the number of packaged into the first line of the lst file
32
  18 jul 2013: writing the number of packaged into the first line of the lst file
33
  11 jul 2013: added a switch to 7za to make it case insensitive when extracting lsm files
33
  11 jul 2013: added a switch to 7za to make it case insensitive when extracting lsm files
34
  10 jul 2013: changed unzip calls to 7za (to handle cases when appinfo is compressed with lzma)
34
  10 jul 2013: changed unzip calls to 7za (to handle cases when appinfo is compressed with lzma)
35
  04 feb 2013: added CRC32 support
35
  04 feb 2013: added CRC32 support
36
  22 sep 2012: forked 1st version from FDUPDATE builder
36
  22 sep 2012: forked 1st version from FDUPDATE builder
37
*/
37
*/
38
 
38
 
39
$PVER = "20220228";
39
$PVER = "20220228";
40
 
40
 
41
 
41
 
42
// computes the BSD sum of a file and returns it
42
// computes the BSD sum of a file and returns it
43
function file2bsum($fname) {
43
function file2bsum($fname) {
44
  $result = 0;
44
  $result = 0;
45
 
45
 
46
  $fd = fopen($fname, 'rb');
46
  $fd = fopen($fname, 'rb');
47
  if ($fd === false) return(0);
47
  if ($fd === false) return(0);
48
 
48
 
49
  while (!feof($fd)) {
49
  while (!feof($fd)) {
50
 
50
 
51
    $buff = fread($fd, 1024 * 1024);
51
    $buff = fread($fd, 1024 * 1024);
52
 
52
 
53
    $slen = strlen($buff);
53
    $slen = strlen($buff);
54
    for ($i = 0; $i < $slen; $i++) {
54
    for ($i = 0; $i < $slen; $i++) {
55
      // rotr
55
      // rotr
56
      $result = ($result >> 1) | ($result << 15);
56
      $result = ($result >> 1) | ($result << 15);
57
      // add and truncate to 16 bits
57
      // add and truncate to 16 bits
58
      $result += ord($buff[$i]);
58
      $result += ord($buff[$i]);
59
      $result &= 0xffff;
59
      $result &= 0xffff;
60
    }
60
    }
61
  }
61
  }
62
 
62
 
63
  fclose($fd);
63
  fclose($fd);
64
  return($result);
64
  return($result);
65
}
65
}
66
 
66
 
67
 
67
 
68
// translates a version string into a array of integer values.
68
// translates a version string into a array of integer values.
69
// Accepted formats follow:
69
// Accepted formats follow:
70
//    300.12.1
70
//    300.12.1
71
//    1
71
//    1
72
//    12.2.34.2-4.5
72
//    12.2.34.2-4.5
73
//    1.2c
73
//    1.2c
74
//    1.01 beta+3
74
//    1.01 beta+3
75
//    2013-12-31
75
//    2013-12-31
76
//    20220222 alpha
76
//    20220222 alpha
77
function vertoarr($verstr) {
77
function vertoarr($verstr) {
78
  $subver = array(0,0,0,0);
78
  $subver = array(0,0,0,0);
79
 
79
 
80
  // switch string to lcase for easier processing and trim any leading or trailing white spaces
80
  // switch string to lcase for easier processing and trim any leading or trailing white spaces
81
  $verstr = strtolower(trim($verstr));
81
  $verstr = strtolower(trim($verstr));
82
 
82
 
83
  // replace all '-' and '/' characters to '.' (uniformization of sub-version parts delimiters)
83
  // replace all '-' and '/' characters to '.' (uniformization of sub-version parts delimiters)
84
  $verstr = strtr($verstr, '-/', '..');
84
  $verstr = strtr($verstr, '-/', '..');
85
 
85
 
86
  // is there a subversion value? (for example "+4" in "1.05+4")
86
  // is there a subversion value? (for example "+4" in "1.05+4")
87
  $i = strrpos($verstr, '+', 1);
87
  $i = strrpos($verstr, '+', 1);
88
  if ($i !== false) {
88
  if ($i !== false) {
89
    // validate the svar-version is a proper integer
89
    // validate the svar-version is a proper integer
90
    $svarver = substr($verstr, $i + 1);
90
    $svarver = substr($verstr, $i + 1);
91
    if (! preg_match('/[1-9][0-9]*/', $svarver)) {
91
    if (! preg_match('/[1-9][0-9]*/', $svarver)) {
92
      return(false);
92
      return(false);
93
    }
93
    }
94
    $subver[3] = intval($svarver); // set the +rev as a very minor item
94
    $subver[3] = intval($svarver); // set the +rev as a very minor item
95
    $verstr = substr($verstr, 0, $i);
95
    $verstr = substr($verstr, 0, $i);
96
  }
96
  }
97
 
97
 
98
  // NESticls hack: version "x.xx" is translated to "0.44"... that sucks but that's how it is.
98
  // NESticls hack: version "x.xx" is translated to "0.44"... that sucks but that's how it is.
99
  // ref: https://web.archive.org/web/20070205074631/http://www.zophar.net/NESticle/
99
  // ref: https://web.archive.org/web/20070205074631/http://www.zophar.net/NESticle/
100
  if ($verstr == 'x.xx') $verstr = '0.44';
100
  if ($verstr == 'x.xx') $verstr = '0.44';
101
 
101
 
102
  // beta reordering: convert "beta 0.95" to "0.95 beta"
102
  // beta reordering: convert "beta 0.95" to "0.95 beta"
103
  if (preg_match('/^beta /', $verstr)) $verstr = substr($verstr, 5) . ' beta';
103
  if (preg_match('/^beta /', $verstr)) $verstr = substr($verstr, 5) . ' beta';
104
 
104
 
105
  // any occurence of alpha,beta,gamma,delta etc preceded by a digit should have a space separator added
105
  // any occurence of alpha,beta,gamma,delta etc preceded by a digit should have a space separator added
106
  // example: "2.6.0pre9" becomes "2.6.0 pre9"
106
  // example: "2.6.0pre9" becomes "2.6.0 pre9"
107
  $verstr = preg_replace('/([0-9])(alpha|beta|gamma|delta|pre|rc|patch)/', '$1 $2', $verstr);
107
  $verstr = preg_replace('/([0-9])(alpha|beta|gamma|delta|pre|rc|patch)/', '$1 $2', $verstr);
108
 
108
 
109
  // same as above, but this time adding a trailing space separator
109
  // same as above, but this time adding a trailing space separator
110
  // example: "2.6.0 pre9" becomes "2.6.0 pre 9"
110
  // example: "2.6.0 pre9" becomes "2.6.0 pre 9"
111
  $verstr = preg_replace('/(alpha|beta|gamma|delta|pre|rc|patch)([0-9])/', '$1 $2', $verstr);
111
  $verstr = preg_replace('/(alpha|beta|gamma|delta|pre|rc|patch)([0-9])/', '$1 $2', $verstr);
112
 
112
 
113
  // is the version ending with ' alpha', 'beta', etc?
113
  // is the version ending with ' alpha', 'beta', etc?
114
  if (preg_match('/ (alpha|beta|gamma|delta|pre|rc|patch)( [0-9]{1,4}){0,1}$/', $verstr)) {
114
  if (preg_match('/ (alpha|beta|gamma|delta|pre|rc|patch)( [0-9]{1,4}){0,1}$/', $verstr)) {
115
    // if there is a trailing beta-number, process it first
115
    // if there is a trailing beta-number, process it first
116
    if (preg_match('/ [0-9]{1,4}$/', $verstr)) {
116
    if (preg_match('/ [0-9]{1,4}$/', $verstr)) {
117
      $i = strrpos($verstr, ' ');
117
      $i = strrpos($verstr, ' ');
118
      $subver[2] = intval(substr($verstr, $i + 1));
118
      $subver[2] = intval(substr($verstr, $i + 1));
119
      $verstr = trim(substr($verstr, 0, $i));
119
      $verstr = trim(substr($verstr, 0, $i));
120
    }
120
    }
121
    $i = strrpos($verstr, ' ');
121
    $i = strrpos($verstr, ' ');
122
    $greek = substr($verstr, $i + 1);
122
    $greek = substr($verstr, $i + 1);
123
    $verstr = trim(substr($verstr, 0, $i));
123
    $verstr = trim(substr($verstr, 0, $i));
124
    if ($greek == 'alpha') {
124
    if ($greek == 'alpha') {
125
      $subver[1] = 1;
125
      $subver[1] = 1;
126
    } else if ($greek == 'beta') {
126
    } else if ($greek == 'beta') {
127
      $subver[1] = 2;
127
      $subver[1] = 2;
128
    } else if ($greek == 'gamma') {
128
    } else if ($greek == 'gamma') {
129
      $subver[1] = 3;
129
      $subver[1] = 3;
130
    } else if ($greek == 'delta') {
130
    } else if ($greek == 'delta') {
131
      $subver[1] = 4;
131
      $subver[1] = 4;
132
    } else if ($greek == 'pre') {
132
    } else if ($greek == 'pre') {
133
      $subver[1] = 5;
133
      $subver[1] = 5;
134
    } else if ($greek == 'rc') {
134
    } else if ($greek == 'rc') {
135
      $subver[1] = 6;
135
      $subver[1] = 6;
136
    } else if ($greek == 'patch') { // this is a POST-release version, as opposed to all above that are PRE-release versions
136
    } else if ($greek == 'patch') { // this is a POST-release version, as opposed to all above that are PRE-release versions
137
      $subver[1] = 99;
137
      $subver[1] = 99;
138
    } else {
138
    } else {
139
      return(false);
139
      return(false);
140
    }
140
    }
141
  } else {
141
  } else {
142
    $subver[1] = 98; // one less than the 'patch' level
142
    $subver[1] = 98; // one less than the 'patch' level
143
  }
143
  }
144
 
144
 
145
  // does the version string have a single-letter subversion? (1.0c)
145
  // does the version string have a single-letter subversion? (1.0c)
146
  if (preg_match('/[a-z]$/', $verstr)) {
146
  if (preg_match('/[a-z]$/', $verstr)) {
147
    $subver[0] = ord(substr($verstr, -1));
147
    $subver[0] = ord(substr($verstr, -1));
148
    $verstr = substr_replace($verstr, '', -1); // remove last character from string
148
    $verstr = substr_replace($verstr, '', -1); // remove last character from string
149
  }
149
  }
150
 
150
 
151
  // convert "30-jan-99", "1999-jan-30" and "30-jan-1999" versions to "30jan99" or "30jan1999"
151
  // convert "30-jan-99", "1999-jan-30" and "30-jan-1999" versions to "30jan99" or "30jan1999"
152
  // note that dashes have already been replaced by dots
152
  // note that dashes have already been replaced by dots
153
  if (preg_match('/^([0-9][0-9]){1,2}\.(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\.([0-9][0-9]){1,2}$/', $verstr)) {
153
  if (preg_match('/^([0-9][0-9]){1,2}\.(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\.([0-9][0-9]){1,2}$/', $verstr)) {
154
    $verstr = str_replace('.', '', $verstr);
154
    $verstr = str_replace('.', '', $verstr);
155
  }
155
  }
156
 
156
 
157
  // convert "2009mar17" versions to "17mar2009"
157
  // convert "2009mar17" versions to "17mar2009"
158
  if (preg_match('/^[0-9]{4}(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[0-9]{2}$/', $verstr)) {
158
  if (preg_match('/^[0-9]{4}(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[0-9]{2}$/', $verstr)) {
159
    $dy = substr($verstr, 7);
159
    $dy = substr($verstr, 7);
160
    $mo = substr($verstr, 4, 3);
160
    $mo = substr($verstr, 4, 3);
161
    $ye = substr($verstr, 0, 4);
161
    $ye = substr($verstr, 0, 4);
162
    $verstr = "{$dy}{$mo}{$ye}";
162
    $verstr = "{$dy}{$mo}{$ye}";
163
  }
163
  }
164
 
164
 
165
  // convert "30jan99" versions to 99.1.30 and "30jan1999" to 1999.1.30
165
  // convert "30jan99" versions to 99.1.30 and "30jan1999" to 1999.1.30
166
  if (preg_match('/^[0-3][0-9](jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)([0-9][0-9]){1,2}$/', $verstr)) {
166
  if (preg_match('/^[0-3][0-9](jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)([0-9][0-9]){1,2}$/', $verstr)) {
167
    $months = array('jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12);
167
    $months = array('jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12);
168
    $dy = substr($verstr, 0, 2);
168
    $dy = substr($verstr, 0, 2);
169
    $mo = $months[substr($verstr, 2, 3)];
169
    $mo = $months[substr($verstr, 2, 3)];
170
    $ye = substr($verstr, 5);
170
    $ye = substr($verstr, 5);
171
    $verstr = "{$ye}.{$mo}.{$dy}";
171
    $verstr = "{$ye}.{$mo}.{$dy}";
172
  }
172
  }
173
 
173
 
174
  // validate the format is supported, should be something no more complex than 1.05.3.33
174
  // validate the format is supported, should be something no more complex than 1.05.3.33
175
  if (! preg_match('/^[0-9][0-9.]{0,20}$/', $verstr)) {
175
  if (! preg_match('/^[0-9][0-9.]{0,20}$/', $verstr)) {
176
    return(false);
176
    return(false);
177
  }
177
  }
178
 
178
 
179
  // NOTE: a zero right after a separator and trailed with a digit (as in 1.01)
179
  // NOTE: a zero right after a separator and trailed with a digit (as in 1.01)
180
  //       has a special meaning
180
  //       has a special meaning
181
  $exploded = explode('.', $verstr);
181
  $exploded = explode('.', $verstr);
182
  if (count($exploded) > 16) {
182
  if (count($exploded) > 16) {
183
    return(false);
183
    return(false);
184
  }
184
  }
185
  $exploded[16] = $subver[0]; // a-z (1.0c)
185
  $exploded[16] = $subver[0]; // a-z (1.0c)
186
  $exploded[17] = $subver[1]; // alpha/beta/gamma/delta/rc/pre
186
  $exploded[17] = $subver[1]; // alpha/beta/gamma/delta/rc/pre
187
  $exploded[18] = $subver[2]; // alpha-beta-gamma subversion (eg. "beta 9")
187
  $exploded[18] = $subver[2]; // alpha-beta-gamma subversion (eg. "beta 9")
188
  $exploded[19] = $subver[3]; // svar-ver (1.0+5)
188
  $exploded[19] = $subver[3]; // svar-ver (1.0+5)
189
  for ($i = 0; $i < 20; $i++) if (empty($exploded[$i])) $exploded[$i] = '0';
189
  for ($i = 0; $i < 20; $i++) if (empty($exploded[$i])) $exploded[$i] = '0';
190
 
190
 
191
  ksort($exploded);
191
  ksort($exploded);
192
 
192
 
193
  return($exploded);
193
  return($exploded);
194
}
194
}
195
 
195
 
196
 
196
 
197
function dos_version_compare($v1, $v2) {
197
function dos_version_compare($v1, $v2) {
198
  $v1arr = vertoarr($v1);
198
  $v1arr = vertoarr($v1);
199
  $v2arr = vertoarr($v2);
199
  $v2arr = vertoarr($v2);
200
  for ($i = 0; $i < count($v1arr); $i++) {
200
  for ($i = 0; $i < count($v1arr); $i++) {
201
    if ($v1arr[$i] > $v2arr[$i]) return(1);
201
    if ($v1arr[$i] > $v2arr[$i]) return(1);
202
    if ($v1arr[$i] < $v2arr[$i]) return(-1);
202
    if ($v1arr[$i] < $v2arr[$i]) return(-1);
203
  }
203
  }
204
  return(0);
204
  return(0);
205
}
205
}
206
 
206
 
207
 
207
 
208
// reads file fil from zip archive z and returns its content, or false on error
208
// reads file fil from zip archive z and returns its content, or false on error
209
function read_file_from_zip($z, $fil) {
209
function read_file_from_zip($z, $fil) {
210
  $zip = new ZipArchive;
210
  $zip = new ZipArchive;
211
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
211
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
212
    echo "ERROR: failed to open zip file '{$z}'\n";
212
    echo "ERROR: failed to open zip file '{$z}'\n";
213
    return(false);
213
    return(false);
214
  }
214
  }
215
 
215
 
216
  // load the appinfo/pkgname.lsm file
216
  // load the appinfo/pkgname.lsm file
217
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
217
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
218
 
218
 
219
  $zip->close();
219
  $zip->close();
220
  return($res);
220
  return($res);
221
}
221
}
222
 
222
 
223
 
223
 
224
function read_list_of_files_in_zip($z) {
224
function read_list_of_files_in_zip($z) {
225
  $zip = new ZipArchive;
225
  $zip = new ZipArchive;
226
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
226
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
227
    echo "ERROR: failed to open zip file '{$z}'\n";
227
    echo "ERROR: failed to open zip file '{$z}'\n";
228
    return(false);
228
    return(false);
229
  }
229
  }
230
 
230
 
231
  $res = array();
231
  $res = array();
232
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
232
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
233
 
233
 
234
  $zip->close();
234
  $zip->close();
235
  return($res);
235
  return($res);
236
}
236
}
237
 
237
 
238
 
238
 
239
// reads a LSM string and returns it in the form of an array
239
// reads a LSM string and returns it in the form of an array
240
function parse_lsm($s) {
240
function parse_lsm($s) {
241
  $res = array();
241
  $res = array();
242
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
242
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
243
    // the line is "token: value", let's find the colon
243
    // the line is "token: value", let's find the colon
244
    $colpos = strpos($l, ':');
244
    $colpos = strpos($l, ':');
245
    if (($colpos === false) || ($colpos === 0)) continue;
245
    if (($colpos === false) || ($colpos === 0)) continue;
246
    $tok = strtolower(trim(substr($l, 0, $colpos)));
246
    $tok = strtolower(trim(substr($l, 0, $colpos)));
247
    $val = trim(substr($l, $colpos + 1));
247
    $val = trim(substr($l, $colpos + 1));
248
    $res[$tok] = $val;
248
    $res[$tok] = $val;
249
  }
249
  }
250
  return($res);
250
  return($res);
251
}
251
}
252
 
252
 
253
 
253
 
254
// on PHP 8+ there is str_starts_with(), but not on PHP 7 so I use this
254
// on PHP 8+ there is str_starts_with(), but not on PHP 7 so I use this
255
function str_head_is($haystack, $needle) {
255
function str_head_is($haystack, $needle) {
256
  return strpos($haystack, $needle) === 0;
256
  return strpos($haystack, $needle) === 0;
257
}
257
}
258
 
258
 
259
 
259
 
260
// returns an array that contains CORE packages (populated from the core subdirectory in pkgdir)
260
// returns an array that contains CORE packages (populated from the core subdirectory in pkgdir)
261
function load_core_list($repodir) {
261
function load_core_list($repodir) {
262
  $res = array();
262
  $res = array();
263
 
263
 
264
  foreach (scandir($repodir . '/core/') as $f) {
264
  foreach (scandir($repodir . '/core/') as $f) {
265
    if (!preg_match('/\.svp$/', $f)) continue;
265
    if (!preg_match('/\.svp$/', $f)) continue;
266
    $res[] = explode('.', $f)[0];
266
    $res[] = explode('.', $f)[0];
267
  }
267
  }
268
  return($res);
268
  return($res);
269
}
269
}
270
 
270
 
271
 
271
 
272
// ***************** MAIN ROUTINE *********************************************
272
// ***************** MAIN ROUTINE *********************************************
273
 
273
 
274
//echo "SvarDOS repository index generator ver {$PVER}\n";
274
//echo "SvarDOS repository index generator ver {$PVER}\n";
275
 
275
 
276
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
276
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
277
  echo "usage: php buildidx.php repodir\n";
277
  echo "usage: php buildidx.php repodir\n";
278
  exit(1);
278
  exit(1);
279
}
279
}
280
 
280
 
281
$repodir = $_SERVER['argv'][1];
281
$repodir = $_SERVER['argv'][1];
282
 
282
 
283
$pkgfiles = scandir($repodir);
283
$pkgfiles = scandir($repodir);
284
$pkgcount = 0;
284
$pkgcount = 0;
285
 
285
 
286
 
286
 
287
// load the list of CORE and MSDOS_COMPAT packages
287
// load the list of CORE and MSDOS_COMPAT packages
288
 
288
 
289
$core_packages_list = load_core_list($repodir);
289
$core_packages_list = load_core_list($repodir);
290
$msdos_compat_list = explode(' ', 'append assign attrib chkdsk choice comp cpidos debug defrag deltree diskcomp diskcopy display edit edlin exe2bin fc fdapm fdisk find format help himemx kernel keyb label localcfg mem mirror mode more move nlsfunc print replace share shsucdx sort svarcom swsubst tree undelete unformat xcopy');
290
$msdos_compat_list = explode(' ', 'append assign attrib callver chkdsk choice comp cpidos debug defrag deltree diskcomp diskcopy display edit edlin exe2bin fc fdapm fdisk find format help himemx kernel keyb label localcfg mem mirror mode more move nlsfunc print replace share shsucdx sort svarcom swsubst tree undelete unformat xcopy');
291
 
291
 
292
// do a list of all svp packages with their available versions and descriptions
292
// do a list of all svp packages with their available versions and descriptions
293
 
293
 
294
$pkgdb = array();
294
$pkgdb = array();
295
foreach ($pkgfiles as $fname) {
295
foreach ($pkgfiles as $fname) {
296
 
296
 
297
  // zip files (ie. source archives)
297
  // zip files (ie. source archives)
298
  if (preg_match('/\.zip$/', $fname)) {
298
  if (preg_match('/\.zip$/', $fname)) {
299
    // the zip archive should contain at least one file
299
    // the zip archive should contain at least one file
300
    if (count(read_list_of_files_in_zip($repodir . '/' . $fname)) < 1) echo "WARNING: source archive {$fname} contains no files (either empty or corrupted)\n";
300
    if (count(read_list_of_files_in_zip($repodir . '/' . $fname)) < 1) echo "WARNING: source archive {$fname} contains no files (either empty or corrupted)\n";
301
    // check that the file relates to an existing svp package
301
    // check that the file relates to an existing svp package
302
    $svpfname = preg_replace('/zip$/', 'svp', $fname);
302
    $svpfname = preg_replace('/zip$/', 'svp', $fname);
303
    if (!file_exists($repodir . '/' . $svpfname)) echo "ERROR: orphaned source archive '{$fname}' (no matching svp file, expecting a package named '{$svpfname}')\n";
303
    if (!file_exists($repodir . '/' . $svpfname)) echo "ERROR: orphaned source archive '{$fname}' (no matching svp file, expecting a package named '{$svpfname}')\n";
304
    // that is for zip files
304
    // that is for zip files
305
    continue;
305
    continue;
306
  }
306
  }
307
 
307
 
308
  // skip (and warn about) non-svp
308
  // skip (and warn about) non-svp
309
  if (!preg_match('/\.svp$/', $fname)) {
309
  if (!preg_match('/\.svp$/', $fname)) {
310
    $okfiles = array('.', '..', '_cats.json', '_index.json', '_buildidx.log', 'core');
310
    $okfiles = array('.', '..', '_cats.json', '_index.json', '_buildidx.log', 'core');
311
    if (array_search($fname, $okfiles) !== false) continue;
311
    if (array_search($fname, $okfiles) !== false) continue;
312
    echo "WARNING: wild file '{$fname} (this is either an useless file that should be removed, or a misnamed package or source archive)'\n";
312
    echo "WARNING: wild file '{$fname} (this is either an useless file that should be removed, or a misnamed package or source archive)'\n";
313
    continue;
313
    continue;
314
  }
314
  }
315
 
315
 
316
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
316
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
317
    echo "ERROR: {$fname} has a very weird name\n";
317
    echo "ERROR: {$fname} has a very weird name\n";
318
    continue;
318
    continue;
319
  }
319
  }
320
 
320
 
321
  $path_parts = pathinfo($fname);
321
  $path_parts = pathinfo($fname);
322
  $pkgnam = explode('-', $path_parts['filename'])[0];
322
  $pkgnam = explode('-', $path_parts['filename'])[0];
323
  $pkgfullpath = realpath($repodir . '/' . $fname);
323
  $pkgfullpath = realpath($repodir . '/' . $fname);
324
 
324
 
325
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
325
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
326
  if ($lsm == false) {
326
  if ($lsm == false) {
327
    echo "ERROR: {$fname} does not contain an LSM file at the expected location\n";
327
    echo "ERROR: {$fname} does not contain an LSM file at the expected location\n";
328
    continue;
328
    continue;
329
  }
329
  }
330
  $lsmarray = parse_lsm($lsm);
330
  $lsmarray = parse_lsm($lsm);
331
  if (empty($lsmarray['version'])) {
331
  if (empty($lsmarray['version'])) {
332
    echo "ERROR: lsm file in {$fname} does not contain a version\n";
332
    echo "ERROR: lsm file in {$fname} does not contain a version\n";
333
    continue;
333
    continue;
334
  }
334
  }
335
  if (strlen($lsmarray['version']) > 16) {
335
  if (strlen($lsmarray['version']) > 16) {
336
    echo "ERROR: version string in lsm file of {$fname} is too long (16 chars max)\n";
336
    echo "ERROR: version string in lsm file of {$fname} is too long (16 chars max)\n";
337
    continue;
337
    continue;
338
  }
338
  }
339
  if (empty($lsmarray['description'])) {
339
  if (empty($lsmarray['description'])) {
340
    echo "ERROR: lsm file in {$fname} does not contain a description\n";
340
    echo "ERROR: lsm file in {$fname} does not contain a description\n";
341
    continue;
341
    continue;
342
  }
342
  }
343
 
343
 
344
  // validate the files present in the archive
344
  // validate the files present in the archive
345
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
345
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
346
  $pkgdir = $pkgnam;
346
  $pkgdir = $pkgnam;
347
 
347
 
348
  // special rule for "parent and children" packages
348
  // special rule for "parent and children" packages
349
  if (str_head_is($pkgnam, 'djgpp_')) $pkgdir = 'djgpp'; // djgpp_* packages put their files in djgpp
349
  if (str_head_is($pkgnam, 'djgpp_')) $pkgdir = 'djgpp'; // djgpp_* packages put their files in djgpp
350
  if ($pkgnam == 'fbc_help') $pkgdir = 'fbc'; // FreeBASIC help goes to the FreeBASIC dir
350
  if ($pkgnam == 'fbc_help') $pkgdir = 'fbc'; // FreeBASIC help goes to the FreeBASIC dir
351
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
351
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
352
 
352
 
353
  // array used to detect duplicated entries after lower-case conversion
353
  // array used to detect duplicated entries after lower-case conversion
354
  $duparr = array();
354
  $duparr = array();
355
 
355
 
356
  // will hold the list of categories that this package belongs to
356
  // will hold the list of categories that this package belongs to
357
  $catlist = array();
357
  $catlist = array();
358
 
358
 
359
  foreach ($listoffiles as $f) {
359
  foreach ($listoffiles as $f) {
360
    $f = strtolower($f);
360
    $f = strtolower($f);
361
    $path_array = explode('/', $f);
361
    $path_array = explode('/', $f);
362
    // emit a warning when non-8+3 filenames are spotted and find duplicates
362
    // emit a warning when non-8+3 filenames are spotted and find duplicates
363
    foreach ($path_array as $item) {
363
    foreach ($path_array as $item) {
364
      if (empty($item)) continue; // skip empty items at end of paths (eg. appinfo/)
364
      if (empty($item)) continue; // skip empty items at end of paths (eg. appinfo/)
365
      if (!preg_match("/[a-z0-9!#$%&'()@^_`{}~-]{1,8}(\.[a-z0-9!#$%&'()@^_`{}~-]{1,3}){0,1}/", $item)) {
365
      if (!preg_match("/[a-z0-9!#$%&'()@^_`{}~-]{1,8}(\.[a-z0-9!#$%&'()@^_`{}~-]{1,3}){0,1}/", $item)) {
366
        echo "WARNING: {$fname} contains a non-8+3 path (or weird char): {$item} (in $f)\n";
366
        echo "WARNING: {$fname} contains a non-8+3 path (or weird char): {$item} (in $f)\n";
367
      }
367
      }
368
    }
368
    }
369
    // look for dups
369
    // look for dups
370
    if (array_search($f, $duparr) !== false) {
370
    if (array_search($f, $duparr) !== false) {
371
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
371
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
372
    } else {
372
    } else {
373
      $duparr[] = $f;
373
      $duparr[] = $f;
374
    }
374
    }
375
    // LSM file is ok
375
    // LSM file is ok
376
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
376
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
377
    if ($f === "appinfo/") continue;
377
    if ($f === "appinfo/") continue;
378
    // CORE and MSDOS_COMPAT packages are premium citizens and can do a little more
378
    // CORE and MSDOS_COMPAT packages are premium citizens and can do a little more
379
    $core_or_msdoscompat = 0;
379
    $core_or_msdoscompat = 0;
380
    if (array_search($pkgnam, $core_packages_list) !== false) {
380
    if (array_search($pkgnam, $core_packages_list) !== false) {
381
      $catlist[] = 'core';
381
      $catlist[] = 'core';
382
      $core_or_msdoscompat = 1;
382
      $core_or_msdoscompat = 1;
383
    }
383
    }
384
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
384
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
385
      $catlist[] = 'msdos_compat';
385
      $catlist[] = 'msdos_compat';
386
      $core_or_msdoscompat = 1;
386
      $core_or_msdoscompat = 1;
387
    }
387
    }
388
    if ($core_or_msdoscompat == 1) {
388
    if ($core_or_msdoscompat == 1) {
389
      if (str_head_is($f, 'bin/')) continue;
389
      if (str_head_is($f, 'bin/')) continue;
390
      if (str_head_is($f, 'cpi/')) continue;
390
      if (str_head_is($f, 'cpi/')) continue;
391
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
391
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
392
      if ($f === 'doc/') continue;
392
      if ($f === 'doc/') continue;
393
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
393
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
394
      if ($f === 'nls/') continue;
394
      if ($f === 'nls/') continue;
395
    }
395
    }
396
    // SVARCOM is allowed to have a root-based COMMAND.COM file
396
    // SVARCOM is allowed to have a root-based COMMAND.COM file
397
    if ($pkgnam === 'svarcom') {
397
    if ($pkgnam === 'svarcom') {
398
      if ($f === 'command.com') continue;
398
      if ($f === 'command.com') continue;
399
    }
399
    }
400
    // the help package is allowed to put files in... help
400
    // the help package is allowed to put files in... help
401
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
401
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
402
    // must be category-prefixed file, add it to the list of categories for this package
402
    // must be category-prefixed file, add it to the list of categories for this package
403
    $catlist[] = explode('/', $f)[0];
403
    $catlist[] = explode('/', $f)[0];
404
    // well-known "category" dirs are okay
404
    // well-known "category" dirs are okay
405
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
405
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
406
    if ($f === 'progs/') continue;
406
    if ($f === 'progs/') continue;
407
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
407
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
408
    if ($f === 'devel/') continue;
408
    if ($f === 'devel/') continue;
409
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
409
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
410
    if ($f === 'games/') continue;
410
    if ($f === 'games/') continue;
411
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
411
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
412
    if ($f === 'drivers/') continue;
412
    if ($f === 'drivers/') continue;
413
    echo "WARNING: {$fname} contains a file in an illegal location: {$f}\n";
413
    echo "WARNING: {$fname} contains a file in an illegal location: {$f}\n";
414
  }
414
  }
415
 
415
 
416
  // do I understand the version string?
416
  // do I understand the version string?
417
  if (vertoarr($lsmarray['version']) === false) echo "WARNING: {$fname} parsing of version string failed ('{$lsmarray['version']}')\n";
417
  if (vertoarr($lsmarray['version']) === false) echo "WARNING: {$fname} parsing of version string failed ('{$lsmarray['version']}')\n";
418
 
418
 
419
  $meta['fname'] = $fname;
419
  $meta['fname'] = $fname;
420
  $meta['desc'] = $lsmarray['description'];
420
  $meta['desc'] = $lsmarray['description'];
421
  $meta['cats'] = array_unique($catlist);
421
  $meta['cats'] = array_unique($catlist);
422
 
422
 
423
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
423
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
424
}
424
}
425
 
425
 
426
 
426
 
427
$db = array();
427
$db = array();
428
$cats = array();
428
$cats = array();
429
 
429
 
430
// ******** compute the version-sorted list of packages with a single *********
430
// ******** compute the version-sorted list of packages with a single *********
431
// ******** description and category list for each package ********************
431
// ******** description and category list for each package ********************
432
 
432
 
433
// iterate over each svp package
433
// iterate over each svp package
434
foreach ($pkgdb as $pkg => $versions) {
434
foreach ($pkgdb as $pkg => $versions) {
435
 
435
 
436
  // sort filenames by version, highest first
436
  // sort filenames by version, highest first
437
  uksort($versions, "dos_version_compare");
437
  uksort($versions, "dos_version_compare");
438
  $versions = array_reverse($versions, true);
438
  $versions = array_reverse($versions, true);
439
 
439
 
440
  foreach ($versions as $ver => $meta) {
440
  foreach ($versions as $ver => $meta) {
441
    $fname = $meta['fname'];
441
    $fname = $meta['fname'];
442
    $desc = $meta['desc'];
442
    $desc = $meta['desc'];
443
 
443
 
444
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
444
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
445
 
445
 
446
    $meta2['ver'] = strval($ver);
446
    $meta2['ver'] = strval($ver);
447
    $meta2['bsum'] = $bsum;
447
    $meta2['bsum'] = $bsum;
448
 
448
 
449
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
449
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
450
    if (empty($db[$pkg]['cats'])) {
450
    if (empty($db[$pkg]['cats'])) {
451
      $db[$pkg]['cats'] = $meta['cats'];
451
      $db[$pkg]['cats'] = $meta['cats'];
452
      $cats = array_unique(array_merge($cats, $meta['cats']));
452
      $cats = array_unique(array_merge($cats, $meta['cats']));
453
    }
453
    }
454
    $db[$pkg]['versions'][$fname] = $meta2;
454
    $db[$pkg]['versions'][$fname] = $meta2;
455
  }
455
  }
456
 
456
 
457
  $pkgcount++;
457
  $pkgcount++;
458
 
458
 
459
}
459
}
460
 
460
 
461
if ($pkgcount < 100) echo "WARNING: an unexpectedly low number of packages has been found in the repo ({$pkgcount})\n";
461
if ($pkgcount < 100) echo "WARNING: an unexpectedly low number of packages has been found in the repo ({$pkgcount})\n";
462
 
462
 
463
$json_blob = json_encode($db);
463
$json_blob = json_encode($db);
464
if ($json_blob === false) {
464
if ($json_blob === false) {
465
  echo "ERROR: JSON convertion failed! -> ";
465
  echo "ERROR: JSON convertion failed! -> ";
466
  switch (json_last_error()) {
466
  switch (json_last_error()) {
467
    case JSON_ERROR_DEPTH:
467
    case JSON_ERROR_DEPTH:
468
      echo 'maximum stack depth exceeded';
468
      echo 'maximum stack depth exceeded';
469
      break;
469
      break;
470
    case JSON_ERROR_STATE_MISMATCH:
470
    case JSON_ERROR_STATE_MISMATCH:
471
      echo 'underflow of the modes mismatch';
471
      echo 'underflow of the modes mismatch';
472
      break;
472
      break;
473
    case JSON_ERROR_CTRL_CHAR:
473
    case JSON_ERROR_CTRL_CHAR:
474
      echo 'unexpected control character found';
474
      echo 'unexpected control character found';
475
      break;
475
      break;
476
    case JSON_ERROR_UTF8:
476
    case JSON_ERROR_UTF8:
477
      echo 'malformed utf-8 characters';
477
      echo 'malformed utf-8 characters';
478
      break;
478
      break;
479
    default:
479
    default:
480
      echo "unknown error";
480
      echo "unknown error";
481
      break;
481
      break;
482
  }
482
  }
483
  echo "\n";
483
  echo "\n";
484
}
484
}
485
 
485
 
486
file_put_contents($repodir . '/_index.json', $json_blob);
486
file_put_contents($repodir . '/_index.json', $json_blob);
487
 
487
 
488
$cats_json = json_encode($cats);
488
$cats_json = json_encode($cats);
489
file_put_contents($repodir . '/_cats.json', $cats_json);
489
file_put_contents($repodir . '/_cats.json', $cats_json);
490
 
490
 
491
exit(0);
491
exit(0);
492
 
492
 
493
?>
493
?>
494
 
494