Subversion Repositories SvarDOS

Rev

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

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