Subversion Repositories SvarDOS

Rev

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

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