Subversion Repositories SvarDOS

Rev

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

Rev 1011 Rev 1022
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
  02 mar 2022: (yet to create) freecom allowed to have a COMMAND.COM file without subdirectory
-
 
14
  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
15
  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)
16
  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)
17
  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)
18
  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
19
  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
20
  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
21
  14 feb 2022: packages are expected to have the *.svp extension
20
  14 feb 2022: packages are expected to have the *.svp extension
22
  12 feb 2022: skip source packages from being processed (*.src.zip)
21
  12 feb 2022: skip source packages from being processed (*.src.zip)
23
  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
24
  13 feb 2021: 'title' LSM field is no longer looked after
23
  13 feb 2021: 'title' LSM field is no longer looked after
25
  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
26
  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
27
  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
28
  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)
29
  27 aug 2016: accepting full paths to repos (starting with /...)
28
  27 aug 2016: accepting full paths to repos (starting with /...)
30
  07 dec 2013: rewritten buildidx in ANSI C89
29
  07 dec 2013: rewritten buildidx in ANSI C89
31
  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)
32
  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
33
  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
34
  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
35
  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)
36
  04 feb 2013: added CRC32 support
35
  04 feb 2013: added CRC32 support
37
  22 sep 2012: forked 1st version from FDUPDATE builder
36
  22 sep 2012: forked 1st version from FDUPDATE builder
38
*/
37
*/
39
 
38
 
40
$PVER = "20220302";
39
$PVER = "20220228";
41
 
40
 
42
 
41
 
43
// computes the BSD sum of a file and returns it
42
// computes the BSD sum of a file and returns it
44
function file2bsum($fname) {
43
function file2bsum($fname) {
45
  $result = 0;
44
  $result = 0;
46
 
45
 
47
  $fd = fopen($fname, 'rb');
46
  $fd = fopen($fname, 'rb');
48
  if ($fd === false) return(0);
47
  if ($fd === false) return(0);
49
 
48
 
50
  while (!feof($fd)) {
49
  while (!feof($fd)) {
51
 
50
 
52
    $buff = fread($fd, 1024 * 1024);
51
    $buff = fread($fd, 1024 * 1024);
53
 
52
 
54
    $slen = strlen($buff);
53
    $slen = strlen($buff);
55
    for ($i = 0; $i < $slen; $i++) {
54
    for ($i = 0; $i < $slen; $i++) {
56
      // rotr
55
      // rotr
57
      $result = ($result >> 1) | ($result << 15);
56
      $result = ($result >> 1) | ($result << 15);
58
      // add and truncate to 16 bits
57
      // add and truncate to 16 bits
59
      $result += ord($buff[$i]);
58
      $result += ord($buff[$i]);
60
      $result &= 0xffff;
59
      $result &= 0xffff;
61
    }
60
    }
62
  }
61
  }
63
 
62
 
64
  fclose($fd);
63
  fclose($fd);
65
  return($result);
64
  return($result);
66
}
65
}
67
 
66
 
68
 
67
 
69
// translates a version string into a array of integer values.
68
// translates a version string into a array of integer values.
70
// Accepted formats follow:
69
// Accepted formats follow:
71
//    300.12.1
70
//    300.12.1
72
//    1
71
//    1
73
//    12.2.34.2-4.5
72
//    12.2.34.2-4.5
74
//    1.2c
73
//    1.2c
75
//    1.01 beta+3
74
//    1.01 beta+3
76
//    2013-12-31
75
//    2013-12-31
77
//    20220222 alpha
76
//    20220222 alpha
78
function vertoarr($verstr) {
77
function vertoarr($verstr) {
79
  $subver = array(0,0,0,0);
78
  $subver = array(0,0,0,0);
80
 
79
 
81
  // 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
82
  $verstr = strtolower(trim($verstr));
81
  $verstr = strtolower(trim($verstr));
83
 
82
 
84
  // replace all '-' and '/' characters to '.' (uniformization of sub-version parts delimiters)
83
  // replace all '-' and '/' characters to '.' (uniformization of sub-version parts delimiters)
85
  $verstr = strtr($verstr, '-/', '..');
84
  $verstr = strtr($verstr, '-/', '..');
86
 
85
 
87
  // 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")
88
  $i = strrpos($verstr, '+', 1);
87
  $i = strrpos($verstr, '+', 1);
89
  if ($i !== false) {
88
  if ($i !== false) {
90
    // validate the svar-version is a proper integer
89
    // validate the svar-version is a proper integer
91
    $svarver = substr($verstr, $i + 1);
90
    $svarver = substr($verstr, $i + 1);
92
    if (! preg_match('/[1-9][0-9]*/', $svarver)) {
91
    if (! preg_match('/[1-9][0-9]*/', $svarver)) {
93
      return(false);
92
      return(false);
94
    }
93
    }
95
    $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
96
    $verstr = substr($verstr, 0, $i);
95
    $verstr = substr($verstr, 0, $i);
97
  }
96
  }
98
 
97
 
99
  // 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.
100
  // 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/
101
  if ($verstr == 'x.xx') $verstr = '0.44';
100
  if ($verstr == 'x.xx') $verstr = '0.44';
102
 
101
 
103
  // beta reordering: convert "beta 0.95" to "0.95 beta"
102
  // beta reordering: convert "beta 0.95" to "0.95 beta"
104
  if (preg_match('/^beta /', $verstr)) $verstr = substr($verstr, 5) . ' beta';
103
  if (preg_match('/^beta /', $verstr)) $verstr = substr($verstr, 5) . ' beta';
105
 
104
 
106
  // 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
107
  // example: "2.6.0pre9" becomes "2.6.0 pre9"
106
  // example: "2.6.0pre9" becomes "2.6.0 pre9"
108
  $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);
109
 
108
 
110
  // same as above, but this time adding a trailing space separator
109
  // same as above, but this time adding a trailing space separator
111
  // example: "2.6.0 pre9" becomes "2.6.0 pre 9"
110
  // example: "2.6.0 pre9" becomes "2.6.0 pre 9"
112
  $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);
113
 
112
 
114
  // is the version ending with ' alpha', 'beta', etc?
113
  // is the version ending with ' alpha', 'beta', etc?
115
  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)) {
116
    // if there is a trailing beta-number, process it first
115
    // if there is a trailing beta-number, process it first
117
    if (preg_match('/ [0-9]{1,4}$/', $verstr)) {
116
    if (preg_match('/ [0-9]{1,4}$/', $verstr)) {
118
      $i = strrpos($verstr, ' ');
117
      $i = strrpos($verstr, ' ');
119
      $subver[2] = intval(substr($verstr, $i + 1));
118
      $subver[2] = intval(substr($verstr, $i + 1));
120
      $verstr = trim(substr($verstr, 0, $i));
119
      $verstr = trim(substr($verstr, 0, $i));
121
    }
120
    }
122
    $i = strrpos($verstr, ' ');
121
    $i = strrpos($verstr, ' ');
123
    $greek = substr($verstr, $i + 1);
122
    $greek = substr($verstr, $i + 1);
124
    $verstr = trim(substr($verstr, 0, $i));
123
    $verstr = trim(substr($verstr, 0, $i));
125
    if ($greek == 'alpha') {
124
    if ($greek == 'alpha') {
126
      $subver[1] = 1;
125
      $subver[1] = 1;
127
    } else if ($greek == 'beta') {
126
    } else if ($greek == 'beta') {
128
      $subver[1] = 2;
127
      $subver[1] = 2;
129
    } else if ($greek == 'gamma') {
128
    } else if ($greek == 'gamma') {
130
      $subver[1] = 3;
129
      $subver[1] = 3;
131
    } else if ($greek == 'delta') {
130
    } else if ($greek == 'delta') {
132
      $subver[1] = 4;
131
      $subver[1] = 4;
133
    } else if ($greek == 'pre') {
132
    } else if ($greek == 'pre') {
134
      $subver[1] = 5;
133
      $subver[1] = 5;
135
    } else if ($greek == 'rc') {
134
    } else if ($greek == 'rc') {
136
      $subver[1] = 6;
135
      $subver[1] = 6;
137
    } 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
138
      $subver[1] = 99;
137
      $subver[1] = 99;
139
    } else {
138
    } else {
140
      return(false);
139
      return(false);
141
    }
140
    }
142
  } else {
141
  } else {
143
    $subver[1] = 98; // one less than the 'patch' level
142
    $subver[1] = 98; // one less than the 'patch' level
144
  }
143
  }
145
 
144
 
146
  // does the version string have a single-letter subversion? (1.0c)
145
  // does the version string have a single-letter subversion? (1.0c)
147
  if (preg_match('/[a-z]$/', $verstr)) {
146
  if (preg_match('/[a-z]$/', $verstr)) {
148
    $subver[0] = ord(substr($verstr, -1));
147
    $subver[0] = ord(substr($verstr, -1));
149
    $verstr = substr_replace($verstr, '', -1); // remove last character from string
148
    $verstr = substr_replace($verstr, '', -1); // remove last character from string
150
  }
149
  }
151
 
150
 
152
  // 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"
153
  // note that dashes have already been replaced by dots
152
  // note that dashes have already been replaced by dots
154
  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)) {
155
    $verstr = str_replace('.', '', $verstr);
154
    $verstr = str_replace('.', '', $verstr);
156
  }
155
  }
157
 
156
 
158
  // convert "2009mar17" versions to "17mar2009"
157
  // convert "2009mar17" versions to "17mar2009"
159
  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)) {
160
    $dy = substr($verstr, 7);
159
    $dy = substr($verstr, 7);
161
    $mo = substr($verstr, 4, 3);
160
    $mo = substr($verstr, 4, 3);
162
    $ye = substr($verstr, 0, 4);
161
    $ye = substr($verstr, 0, 4);
163
    $verstr = "{$dy}{$mo}{$ye}";
162
    $verstr = "{$dy}{$mo}{$ye}";
164
  }
163
  }
165
 
164
 
166
  // 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
167
  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)) {
168
    $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);
169
    $dy = substr($verstr, 0, 2);
168
    $dy = substr($verstr, 0, 2);
170
    $mo = $months[substr($verstr, 2, 3)];
169
    $mo = $months[substr($verstr, 2, 3)];
171
    $ye = substr($verstr, 5);
170
    $ye = substr($verstr, 5);
172
    $verstr = "{$ye}.{$mo}.{$dy}";
171
    $verstr = "{$ye}.{$mo}.{$dy}";
173
  }
172
  }
174
 
173
 
175
  // 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
176
  if (! preg_match('/^[0-9][0-9.]{0,20}$/', $verstr)) {
175
  if (! preg_match('/^[0-9][0-9.]{0,20}$/', $verstr)) {
177
    return(false);
176
    return(false);
178
  }
177
  }
179
 
178
 
180
  // 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)
181
  //       has a special meaning
180
  //       has a special meaning
182
  $exploded = explode('.', $verstr);
181
  $exploded = explode('.', $verstr);
183
  if (count($exploded) > 16) {
182
  if (count($exploded) > 16) {
184
    return(false);
183
    return(false);
185
  }
184
  }
186
  $exploded[16] = $subver[0]; // a-z (1.0c)
185
  $exploded[16] = $subver[0]; // a-z (1.0c)
187
  $exploded[17] = $subver[1]; // alpha/beta/gamma/delta/rc/pre
186
  $exploded[17] = $subver[1]; // alpha/beta/gamma/delta/rc/pre
188
  $exploded[18] = $subver[2]; // alpha-beta-gamma subversion (eg. "beta 9")
187
  $exploded[18] = $subver[2]; // alpha-beta-gamma subversion (eg. "beta 9")
189
  $exploded[19] = $subver[3]; // svar-ver (1.0+5)
188
  $exploded[19] = $subver[3]; // svar-ver (1.0+5)
190
  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';
191
 
190
 
192
  ksort($exploded);
191
  ksort($exploded);
193
 
192
 
194
  return($exploded);
193
  return($exploded);
195
}
194
}
196
 
195
 
197
 
196
 
198
function dos_version_compare($v1, $v2) {
197
function dos_version_compare($v1, $v2) {
199
  $v1arr = vertoarr($v1);
198
  $v1arr = vertoarr($v1);
200
  $v2arr = vertoarr($v2);
199
  $v2arr = vertoarr($v2);
201
  for ($i = 0; $i < count($v1arr); $i++) {
200
  for ($i = 0; $i < count($v1arr); $i++) {
202
    if ($v1arr[$i] > $v2arr[$i]) return(1);
201
    if ($v1arr[$i] > $v2arr[$i]) return(1);
203
    if ($v1arr[$i] < $v2arr[$i]) return(-1);
202
    if ($v1arr[$i] < $v2arr[$i]) return(-1);
204
  }
203
  }
205
  return(0);
204
  return(0);
206
}
205
}
207
 
206
 
208
 
207
 
209
// 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
210
function read_file_from_zip($z, $fil) {
209
function read_file_from_zip($z, $fil) {
211
  $zip = new ZipArchive;
210
  $zip = new ZipArchive;
212
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
211
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
213
    echo "ERROR: failed to open zip file '{$z}'\n";
212
    echo "ERROR: failed to open zip file '{$z}'\n";
214
    return(false);
213
    return(false);
215
  }
214
  }
216
 
215
 
217
  // load the appinfo/pkgname.lsm file
216
  // load the appinfo/pkgname.lsm file
218
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
217
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
219
 
218
 
220
  $zip->close();
219
  $zip->close();
221
  return($res);
220
  return($res);
222
}
221
}
223
 
222
 
224
 
223
 
225
function read_list_of_files_in_zip($z) {
224
function read_list_of_files_in_zip($z) {
226
  $zip = new ZipArchive;
225
  $zip = new ZipArchive;
227
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
226
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
228
    echo "ERROR: failed to open zip file '{$z}'\n";
227
    echo "ERROR: failed to open zip file '{$z}'\n";
229
    return(false);
228
    return(false);
230
  }
229
  }
231
 
230
 
232
  $res = array();
231
  $res = array();
233
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
232
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
234
 
233
 
235
  $zip->close();
234
  $zip->close();
236
  return($res);
235
  return($res);
237
}
236
}
238
 
237
 
239
 
238
 
240
// 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
241
function parse_lsm($s) {
240
function parse_lsm($s) {
242
  $res = array();
241
  $res = array();
243
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
242
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
244
    // the line is "token: value", let's find the colon
243
    // the line is "token: value", let's find the colon
245
    $colpos = strpos($l, ':');
244
    $colpos = strpos($l, ':');
246
    if (($colpos === false) || ($colpos === 0)) continue;
245
    if (($colpos === false) || ($colpos === 0)) continue;
247
    $tok = strtolower(trim(substr($l, 0, $colpos)));
246
    $tok = strtolower(trim(substr($l, 0, $colpos)));
248
    $val = trim(substr($l, $colpos + 1));
247
    $val = trim(substr($l, $colpos + 1));
249
    $res[$tok] = $val;
248
    $res[$tok] = $val;
250
  }
249
  }
251
  return($res);
250
  return($res);
252
}
251
}
253
 
252
 
254
 
253
 
255
// 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
256
function str_head_is($haystack, $needle) {
255
function str_head_is($haystack, $needle) {
257
  return strpos($haystack, $needle) === 0;
256
  return strpos($haystack, $needle) === 0;
258
}
257
}
259
 
258
 
260
 
259
 
261
// 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)
262
function load_core_list($repodir) {
261
function load_core_list($repodir) {
263
  $res = array();
262
  $res = array();
264
 
263
 
265
  foreach (scandir($repodir . '/core/') as $f) {
264
  foreach (scandir($repodir . '/core/') as $f) {
266
    if (!preg_match('/\.svp$/', $f)) continue;
265
    if (!preg_match('/\.svp$/', $f)) continue;
267
    $res[] = explode('.', $f)[0];
266
    $res[] = explode('.', $f)[0];
268
  }
267
  }
269
  return($res);
268
  return($res);
270
}
269
}
271
 
270
 
272
 
271
 
273
// ***************** MAIN ROUTINE *********************************************
272
// ***************** MAIN ROUTINE *********************************************
274
 
273
 
275
//echo "SvarDOS repository index generator ver {$PVER}\n";
274
//echo "SvarDOS repository index generator ver {$PVER}\n";
276
 
275
 
277
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
276
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
278
  echo "usage: php buildidx.php repodir\n";
277
  echo "usage: php buildidx.php repodir\n";
279
  exit(1);
278
  exit(1);
280
}
279
}
281
 
280
 
282
$repodir = $_SERVER['argv'][1];
281
$repodir = $_SERVER['argv'][1];
283
 
282
 
284
$pkgfiles = scandir($repodir);
283
$pkgfiles = scandir($repodir);
285
$pkgcount = 0;
284
$pkgcount = 0;
286
 
285
 
287
 
286
 
288
// load the list of CORE and MSDOS_COMPAT packages
287
// load the list of CORE and MSDOS_COMPAT packages
289
 
288
 
290
$core_packages_list = load_core_list($repodir);
289
$core_packages_list = load_core_list($repodir);
291
$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 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');
292
 
291
 
293
// 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
294
 
293
 
295
$pkgdb = array();
294
$pkgdb = array();
296
foreach ($pkgfiles as $fname) {
295
foreach ($pkgfiles as $fname) {
297
 
296
 
298
  // zip files (ie. source archives)
297
  // zip files (ie. source archives)
299
  if (preg_match('/\.zip$/', $fname)) {
298
  if (preg_match('/\.zip$/', $fname)) {
300
    // the zip archive should contain at least one file
299
    // the zip archive should contain at least one file
301
    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";
302
    // check that the file relates to an existing svp package
301
    // check that the file relates to an existing svp package
303
    $svpfname = preg_replace('/zip$/', 'svp', $fname);
302
    $svpfname = preg_replace('/zip$/', 'svp', $fname);
304
    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";
305
    // that is for zip files
304
    // that is for zip files
306
    continue;
305
    continue;
307
  }
306
  }
308
 
307
 
309
  // skip (and warn about) non-svp
308
  // skip (and warn about) non-svp
310
  if (!preg_match('/\.svp$/', $fname)) {
309
  if (!preg_match('/\.svp$/', $fname)) {
311
    $okfiles = array('.', '..', '_cats.json', '_index.json', '_buildidx.log', 'core');
310
    $okfiles = array('.', '..', '_cats.json', '_index.json', '_buildidx.log', 'core');
312
    if (array_search($fname, $okfiles) !== false) continue;
311
    if (array_search($fname, $okfiles) !== false) continue;
313
    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";
314
    continue;
313
    continue;
315
  }
314
  }
316
 
315
 
317
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
316
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
318
    echo "ERROR: {$fname} has a very weird name\n";
317
    echo "ERROR: {$fname} has a very weird name\n";
319
    continue;
318
    continue;
320
  }
319
  }
321
 
320
 
322
  $path_parts = pathinfo($fname);
321
  $path_parts = pathinfo($fname);
323
  $pkgnam = explode('-', $path_parts['filename'])[0];
322
  $pkgnam = explode('-', $path_parts['filename'])[0];
324
  $pkgfullpath = realpath($repodir . '/' . $fname);
323
  $pkgfullpath = realpath($repodir . '/' . $fname);
325
 
324
 
326
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
325
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
327
  if ($lsm == false) {
326
  if ($lsm == false) {
328
    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";
329
    continue;
328
    continue;
330
  }
329
  }
331
  $lsmarray = parse_lsm($lsm);
330
  $lsmarray = parse_lsm($lsm);
332
  if (empty($lsmarray['version'])) {
331
  if (empty($lsmarray['version'])) {
333
    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";
334
    continue;
333
    continue;
335
  }
334
  }
336
  if (strlen($lsmarray['version']) > 16) {
335
  if (strlen($lsmarray['version']) > 16) {
337
    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";
338
    continue;
337
    continue;
339
  }
338
  }
340
  if (empty($lsmarray['description'])) {
339
  if (empty($lsmarray['description'])) {
341
    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";
342
    continue;
341
    continue;
343
  }
342
  }
344
 
343
 
345
  // validate the files present in the archive
344
  // validate the files present in the archive
346
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
345
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
347
  $pkgdir = $pkgnam;
346
  $pkgdir = $pkgnam;
348
 
347
 
349
  // special rule for "parent and children" packages
348
  // special rule for "parent and children" packages
350
  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
351
  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
352
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
351
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
353
 
352
 
354
  // array used to detect duplicated entries after lower-case conversion
353
  // array used to detect duplicated entries after lower-case conversion
355
  $duparr = array();
354
  $duparr = array();
356
 
355
 
357
  // will hold the list of categories that this package belongs to
356
  // will hold the list of categories that this package belongs to
358
  $catlist = array();
357
  $catlist = array();
359
 
358
 
360
  foreach ($listoffiles as $f) {
359
  foreach ($listoffiles as $f) {
361
    $f = strtolower($f);
360
    $f = strtolower($f);
362
    $path_array = explode('/', $f);
361
    $path_array = explode('/', $f);
363
    // 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
364
    foreach ($path_array as $item) {
363
    foreach ($path_array as $item) {
365
      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/)
366
      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)) {
367
        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";
368
      }
367
      }
369
    }
368
    }
370
    // look for dups
369
    // look for dups
371
    if (array_search($f, $duparr) !== false) {
370
    if (array_search($f, $duparr) !== false) {
372
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
371
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
373
    } else {
372
    } else {
374
      $duparr[] = $f;
373
      $duparr[] = $f;
375
    }
374
    }
376
    // LSM file is ok
375
    // LSM file is ok
377
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
376
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
378
    if ($f === "appinfo/") continue;
377
    if ($f === "appinfo/") continue;
379
    // 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
380
    $core_or_msdoscompat = 0;
379
    $core_or_msdoscompat = 0;
381
    if (array_search($pkgnam, $core_packages_list) !== false) {
380
    if (array_search($pkgnam, $core_packages_list) !== false) {
382
      $catlist[] = 'core';
381
      $catlist[] = 'core';
383
      $core_or_msdoscompat = 1;
382
      $core_or_msdoscompat = 1;
384
    }
383
    }
385
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
384
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
386
      $catlist[] = 'msdos_compat';
385
      $catlist[] = 'msdos_compat';
387
      $core_or_msdoscompat = 1;
386
      $core_or_msdoscompat = 1;
388
    }
387
    }
389
    if ($core_or_msdoscompat == 1) {
388
    if ($core_or_msdoscompat == 1) {
390
      if (str_head_is($f, 'bin/')) continue;
389
      if (str_head_is($f, 'bin/')) continue;
391
      if (str_head_is($f, 'cpi/')) continue;
390
      if (str_head_is($f, 'cpi/')) continue;
392
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
391
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
393
      if ($f === 'doc/') continue;
392
      if ($f === 'doc/') continue;
394
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
393
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
395
      if ($f === 'nls/') continue;
394
      if ($f === 'nls/') continue;
396
    }
395
    }
397
    // SVARCOM is allowed to have a root-based COMMAND.COM file
396
    // SVARCOM is allowed to have a root-based COMMAND.COM file
398
    if ($pkgnam === 'svarcom') {
397
    if ($pkgnam === 'svarcom') {
399
      if ($f === 'command.com') continue;
398
      if ($f === 'command.com') continue;
400
    }
-
 
401
    // FREECOM is allowed to have a root-based COMMAND.COM file
-
 
402
    if ($pkgnam === 'freecom') {
-
 
403
      if ($f === 'command.com') continue;
-
 
404
    }
399
    }
405
    // the help package is allowed to put files in... help
400
    // the help package is allowed to put files in... help
406
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
401
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
407
    // 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
408
    $catlist[] = explode('/', $f)[0];
403
    $catlist[] = explode('/', $f)[0];
409
    // well-known "category" dirs are okay
404
    // well-known "category" dirs are okay
410
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
405
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
411
    if ($f === 'progs/') continue;
406
    if ($f === 'progs/') continue;
412
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
407
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
413
    if ($f === 'devel/') continue;
408
    if ($f === 'devel/') continue;
414
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
409
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
415
    if ($f === 'games/') continue;
410
    if ($f === 'games/') continue;
416
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
411
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
417
    if ($f === 'drivers/') continue;
412
    if ($f === 'drivers/') continue;
418
    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";
419
  }
414
  }
420
 
415
 
421
  // do I understand the version string?
416
  // do I understand the version string?
422
  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";
423
 
418
 
424
  $meta['fname'] = $fname;
419
  $meta['fname'] = $fname;
425
  $meta['desc'] = $lsmarray['description'];
420
  $meta['desc'] = $lsmarray['description'];
426
  $meta['cats'] = array_unique($catlist);
421
  $meta['cats'] = array_unique($catlist);
427
 
422
 
428
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
423
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
429
}
424
}
430
 
425
 
431
 
426
 
432
$db = array();
427
$db = array();
433
$cats = array();
428
$cats = array();
434
 
429
 
435
// ******** compute the version-sorted list of packages with a single *********
430
// ******** compute the version-sorted list of packages with a single *********
436
// ******** description and category list for each package ********************
431
// ******** description and category list for each package ********************
437
 
432
 
438
// iterate over each svp package
433
// iterate over each svp package
439
foreach ($pkgdb as $pkg => $versions) {
434
foreach ($pkgdb as $pkg => $versions) {
440
 
435
 
441
  // sort filenames by version, highest first
436
  // sort filenames by version, highest first
442
  uksort($versions, "dos_version_compare");
437
  uksort($versions, "dos_version_compare");
443
  $versions = array_reverse($versions, true);
438
  $versions = array_reverse($versions, true);
444
 
439
 
445
  foreach ($versions as $ver => $meta) {
440
  foreach ($versions as $ver => $meta) {
446
    $fname = $meta['fname'];
441
    $fname = $meta['fname'];
447
    $desc = $meta['desc'];
442
    $desc = $meta['desc'];
448
 
443
 
449
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
444
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
450
 
445
 
451
    $meta2['ver'] = strval($ver);
446
    $meta2['ver'] = strval($ver);
452
    $meta2['bsum'] = $bsum;
447
    $meta2['bsum'] = $bsum;
453
 
448
 
454
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
449
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
455
    if (empty($db[$pkg]['cats'])) {
450
    if (empty($db[$pkg]['cats'])) {
456
      $db[$pkg]['cats'] = $meta['cats'];
451
      $db[$pkg]['cats'] = $meta['cats'];
457
      $cats = array_unique(array_merge($cats, $meta['cats']));
452
      $cats = array_unique(array_merge($cats, $meta['cats']));
458
    }
453
    }
459
    $db[$pkg]['versions'][$fname] = $meta2;
454
    $db[$pkg]['versions'][$fname] = $meta2;
460
  }
455
  }
461
 
456
 
462
  $pkgcount++;
457
  $pkgcount++;
463
 
458
 
464
}
459
}
465
 
460
 
466
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";
467
 
462
 
468
$json_blob = json_encode($db);
463
$json_blob = json_encode($db);
469
if ($json_blob === false) {
464
if ($json_blob === false) {
470
  echo "ERROR: JSON convertion failed! -> ";
465
  echo "ERROR: JSON convertion failed! -> ";
471
  switch (json_last_error()) {
466
  switch (json_last_error()) {
472
    case JSON_ERROR_DEPTH:
467
    case JSON_ERROR_DEPTH:
473
      echo 'maximum stack depth exceeded';
468
      echo 'maximum stack depth exceeded';
474
      break;
469
      break;
475
    case JSON_ERROR_STATE_MISMATCH:
470
    case JSON_ERROR_STATE_MISMATCH:
476
      echo 'underflow of the modes mismatch';
471
      echo 'underflow of the modes mismatch';
477
      break;
472
      break;
478
    case JSON_ERROR_CTRL_CHAR:
473
    case JSON_ERROR_CTRL_CHAR:
479
      echo 'unexpected control character found';
474
      echo 'unexpected control character found';
480
      break;
475
      break;
481
    case JSON_ERROR_UTF8:
476
    case JSON_ERROR_UTF8:
482
      echo 'malformed utf-8 characters';
477
      echo 'malformed utf-8 characters';
483
      break;
478
      break;
484
    default:
479
    default:
485
      echo "unknown error";
480
      echo "unknown error";
486
      break;
481
      break;
487
  }
482
  }
488
  echo "\n";
483
  echo "\n";
489
}
484
}
490
 
485
 
491
file_put_contents($repodir . '/_index.json', $json_blob);
486
file_put_contents($repodir . '/_index.json', $json_blob);
492
 
487
 
493
$cats_json = json_encode($cats);
488
$cats_json = json_encode($cats);
494
file_put_contents($repodir . '/_cats.json', $cats_json);
489
file_put_contents($repodir . '/_cats.json', $cats_json);
495
 
490
 
496
exit(0);
491
exit(0);
497
 
492
 
498
?>
493
?>
499
 
494