Subversion Repositories SvarDOS

Rev

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

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