Subversion Repositories SvarDOS

Rev

Rev 927 | Rev 941 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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