Subversion Repositories SvarDOS

Rev

Rev 919 | Rev 921 | 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
 
909 mateusz.vi 36
$PVER = "20220221";
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
 
95
  // is the version ending with ' alpha', 'beta'?
96
  if (preg_match('/ (alpha|beta)$/', $verstr)) {
97
    $i = strrpos($verstr, ' ');
98
    $greek = substr($verstr, $i + 1);
99
    $verstr = trim(substr($verstr, 0, $i));
100
    if ($greek == 'alpha') {
101
      $subver[2] = 1;
102
    } else if ($greek == 'beta') {
103
      $subver[2] = 2;
920 mateusz.vi 104
    } else if ($greek == 'gamma') {
105
      $subver[2] = 3;
106
    } else if ($greek == 'delta') {
107
      $subver[2] = 4;
914 mateusz.vi 108
    } else if ($greek == 'rc') {
920 mateusz.vi 109
      $subver[2] = 5;
912 mateusz.vi 110
    } else {
111
      return(false);
112
    }
914 mateusz.vi 113
  } else {
114
    $subver[2] = 99;
912 mateusz.vi 115
  }
116
 
117
  // does the version string have a single-letter subversion? (1.0c)
118
  if (preg_match('/[a-z]$/', $verstr)) {
119
    $subver[1] = ord(substr($verstr, -1));
120
    $verstr = substr_replace($verstr, '', -1); // remove last character from string
121
  }
122
 
123
  // validate the format is supported, should be something no more complex than 1.05.3.33
919 mateusz.vi 124
  if (! preg_match('/^[0-9][0-9.]{0,20}$/', $verstr)) {
912 mateusz.vi 125
    return(false);
126
  }
127
 
128
  // NOTE: a zero right after a separator and trailed with a digit (as in 1.01)
129
  //       has a special meaning
130
  $exploded = explode('.', $verstr);
131
  if (count($exploded) > 16) {
132
    return(false);
133
  }
134
  $exploded[16] = $subver[0]; // unused yet
135
  $exploded[17] = $subver[1]; // a-z (1.0c)
136
  $exploded[18] = $subver[2]; // alpha/beta
137
  $exploded[19] = $subver[3]; // svar-ver (1.0+5)
138
  for ($i = 0; $i < 20; $i++) if (empty($exploded[$i])) $exploded[$i] = '0';
139
 
140
  ksort($exploded);
141
 
142
  return($exploded);
143
}
144
 
145
 
146
function dos_version_compare($v1, $v2) {
147
  $v1arr = vertoarr($v1);
148
  $v2arr = vertoarr($v2);
149
  for ($i = 0; $i < count($v1arr); $i++) {
150
    $r = strcmp($v1arr[$i], $v2arr[$i]);
151
    if ($r != 0) return($r);
152
  }
153
  return(0);
154
}
155
 
156
 
562 mateuszvis 157
// reads file fil from zip archive z and returns its content, or false on error
158
function read_file_from_zip($z, $fil) {
159
  $zip = new ZipArchive;
160
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
161
    echo "ERROR: failed to open zip file '{$z}'\n";
162
    return(false);
163
  }
164
 
165
  // load the appinfo/pkgname.lsm file
166
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
167
 
168
  $zip->close();
169
  return($res);
170
}
171
 
172
 
731 mateusz.vi 173
function read_list_of_files_in_zip($z) {
174
  $zip = new ZipArchive;
175
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
176
    echo "ERROR: failed to open zip file '{$z}'\n";
177
    return(false);
178
  }
179
 
180
  $res = array();
181
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
182
 
183
  $zip->close();
184
  return($res);
185
}
186
 
187
 
562 mateuszvis 188
// reads a LSM string and returns it in the form of an array
189
function parse_lsm($s) {
190
  $res = array();
191
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
192
    // the line is "token: value", let's find the colon
193
    $colpos = strpos($l, ':');
194
    if (($colpos === false) || ($colpos === 0)) continue;
195
    $tok = strtolower(trim(substr($l, 0, $colpos)));
196
    $val = trim(substr($l, $colpos + 1));
197
    $res[$tok] = $val;
198
  }
199
  return($res);
200
}
201
 
202
 
731 mateusz.vi 203
// on PHP 8+ there is str_starts_with(), but not on PHP 7 so I use this
204
function str_head_is($haystack, $needle) {
205
  return strpos($haystack, $needle) === 0;
206
}
207
 
208
 
791 mateusz.vi 209
// returns an array that contains CORE packages (populated from the core subdirectory in pkgdir)
210
function load_core_list($repodir) {
211
  $res = array();
212
 
213
  foreach (scandir($repodir . '/core/') as $f) {
214
    if (!preg_match('/\.svp$/', $f)) continue;
215
    $res[] = explode('.', $f)[0];
216
  }
217
  return($res);
218
}
219
 
220
 
562 mateuszvis 221
// ***************** MAIN ROUTINE *********************************************
222
 
719 mateusz.vi 223
//echo "SvarDOS repository index generator ver {$PVER}\n";
562 mateuszvis 224
 
225
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
226
  echo "usage: php buildidx.php repodir\n";
227
  exit(1);
228
}
229
 
230
$repodir = $_SERVER['argv'][1];
231
 
232
$pkgfiles = scandir($repodir);
233
$pkgcount = 0;
234
 
738 mateusz.vi 235
 
795 mateusz.vi 236
// load the list of CORE and MSDOS_COMPAT packages
738 mateusz.vi 237
 
791 mateusz.vi 238
$core_packages_list = load_core_list($repodir);
804 bttr 239
$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 240
 
719 mateusz.vi 241
// do a list of all svp packages with their available versions and descriptions
562 mateuszvis 242
 
719 mateusz.vi 243
$pkgdb = array();
244
foreach ($pkgfiles as $fname) {
801 mateusz.vi 245
  if (!preg_match('/\.svp$/i', $fname)) continue; // skip non-svp files
562 mateuszvis 246
 
801 mateusz.vi 247
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
248
    echo "ERROR: {$fname} has a very weird name\n";
249
    continue;
250
  }
251
 
719 mateusz.vi 252
  $path_parts = pathinfo($fname);
253
  $pkgnam = explode('-', $path_parts['filename'])[0];
254
  $pkgfullpath = realpath($repodir . '/' . $fname);
562 mateuszvis 255
 
719 mateusz.vi 256
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
562 mateuszvis 257
  if ($lsm == false) {
802 mateusz.vi 258
    echo "ERROR: {$fname} does not contain an LSM file at the expected location\n";
719 mateusz.vi 259
    continue;
562 mateuszvis 260
  }
261
  $lsmarray = parse_lsm($lsm);
262
  if (empty($lsmarray['version'])) {
719 mateusz.vi 263
    echo "ERROR: lsm file in {$fname} does not contain a version\n";
264
    continue;
562 mateuszvis 265
  }
730 mateusz.vi 266
  if (strlen($lsmarray['version']) > 16) {
737 mateusz.vi 267
    echo "ERROR: version string in lsm file of {$fname} is too long (16 chars max)\n";
730 mateusz.vi 268
    continue;
269
  }
562 mateuszvis 270
  if (empty($lsmarray['description'])) {
719 mateusz.vi 271
    echo "ERROR: lsm file in {$fname} does not contain a description\n";
272
    continue;
562 mateuszvis 273
  }
274
 
731 mateusz.vi 275
  // validate the files present in the archive
276
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
739 mateusz.vi 277
  $pkgdir = $pkgnam;
278
 
768 mateusz.vi 279
  // special rule for "parent and children" packages
280
  if (str_head_is($pkgnam, 'djgpp_')) $pkgdir = 'djgpp'; // djgpp_* packages put their files in djgpp
754 mateusz.vi 281
  if ($pkgnam == 'fbc_help') $pkgdir = 'fbc'; // FreeBASIC help goes to the FreeBASIC dir
802 mateusz.vi 282
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
739 mateusz.vi 283
 
768 mateusz.vi 284
  // array used to detect duplicated entries after lower-case conversion
285
  $duparr = array();
286
 
909 mateusz.vi 287
  // will hold the list of categories that this package belongs to
288
  $catlist = array();
289
 
731 mateusz.vi 290
  foreach ($listoffiles as $f) {
291
    $f = strtolower($f);
768 mateusz.vi 292
    $path_array = explode('/', $f);
293
    // emit a warning when non-8+3 filenames are spotted and find duplicates
294
    foreach ($path_array as $item) {
295
      if (empty($item)) continue; // skip empty items at end of paths (eg. appinfo/)
296
      if (!preg_match("/[a-z0-9!#$%&'()@^_`{}~-]{1,8}(\.[a-z0-9!#$%&'()@^_`{}~-]{1,3}){0,1}/", $item)) {
297
        echo "WARNING: {$fname} contains a non-8+3 path (or weird char): {$item} (in $f)\n";
298
      }
299
    }
300
    // look for dups
301
    if (array_search($f, $duparr) !== false) {
302
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
303
    } else {
304
      $duparr[] = $f;
305
    }
731 mateusz.vi 306
    // LSM file is ok
307
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
308
    if ($f === "appinfo/") continue;
795 mateusz.vi 309
    // CORE and MSDOS_COMPAT packages are premium citizens and can do a little more
909 mateusz.vi 310
    $core_or_msdoscompat = 0;
311
    if (array_search($pkgnam, $core_packages_list) !== false) {
312
      $catlist[] = 'core';
313
      $core_or_msdoscompat = 1;
314
    }
315
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
316
      $catlist[] = 'msdos_compat';
317
      $core_or_msdoscompat = 1;
318
    }
319
    if ($core_or_msdoscompat == 1) {
736 mateusz.vi 320
      if (str_head_is($f, 'bin/')) continue;
779 mateusz.vi 321
      if (str_head_is($f, 'cpi/')) continue;
749 mateusz.vi 322
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
323
      if ($f === 'doc/') continue;
324
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
325
      if ($f === 'nls/') continue;
736 mateusz.vi 326
    }
798 mateusz.vi 327
    // the help package is allowed to put files in... help
328
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
909 mateusz.vi 329
    // must be category-prefixed file, add it to the list of categories for this package
330
    $catlist[] = explode('/', $f)[0];
749 mateusz.vi 331
    // well-known "category" dirs are okay
739 mateusz.vi 332
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
731 mateusz.vi 333
    if ($f === 'progs/') continue;
739 mateusz.vi 334
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
731 mateusz.vi 335
    if ($f === 'devel/') continue;
739 mateusz.vi 336
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
731 mateusz.vi 337
    if ($f === 'games/') continue;
739 mateusz.vi 338
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
731 mateusz.vi 339
    if ($f === 'drivers/') continue;
768 mateusz.vi 340
    echo "WARNING: {$fname} contains a file in an illegal location: {$f}\n";
731 mateusz.vi 341
  }
342
 
912 mateusz.vi 343
  // do I understand the version string?
344
  if (vertoarr($lsmarray['version']) === false) echo "WARNING: {$fname} parsing of version string failed ('{$lsmarray['version']}')\n";
345
 
719 mateusz.vi 346
  $meta['fname'] = $fname;
347
  $meta['desc'] = $lsmarray['description'];
909 mateusz.vi 348
  $meta['cats'] = array_unique($catlist);
719 mateusz.vi 349
 
350
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
351
}
352
 
801 mateusz.vi 353
 
719 mateusz.vi 354
$db = array();
909 mateusz.vi 355
$cats = array();
719 mateusz.vi 356
 
909 mateusz.vi 357
// ******** compute the version-sorted list of packages with a single *********
358
// ******** description and category list for each package ********************
359
 
719 mateusz.vi 360
// iterate over each svp package
361
foreach ($pkgdb as $pkg => $versions) {
362
 
363
  // sort filenames by version, highest first
912 mateusz.vi 364
  uksort($versions, "dos_version_compare");
719 mateusz.vi 365
  $versions = array_reverse($versions, true);
366
 
367
  foreach ($versions as $ver => $meta) {
368
    $fname = $meta['fname'];
369
    $desc = $meta['desc'];
370
 
371
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
372
 
373
    $meta2['ver'] = strval($ver);
374
    $meta2['bsum'] = $bsum;
375
 
376
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
909 mateusz.vi 377
    if (empty($db[$pkg]['cats'])) {
378
      $db[$pkg]['cats'] = $meta['cats'];
379
      $cats = array_unique(array_merge($cats, $meta['cats']));
380
    }
719 mateusz.vi 381
    $db[$pkg]['versions'][$fname] = $meta2;
382
  }
383
 
562 mateuszvis 384
  $pkgcount++;
385
 
386
}
387
 
719 mateusz.vi 388
if ($pkgcount < 100) echo "WARNING: an unexpectedly low number of packages has been found in the repo ({$pkgcount})\n";
562 mateuszvis 389
 
801 mateusz.vi 390
$json_blob = json_encode($db);
391
if ($json_blob === false) {
392
  echo "ERROR: JSON convertion failed! -> ";
393
  switch (json_last_error()) {
394
    case JSON_ERROR_DEPTH:
395
      echo 'maximum stack depth exceeded';
396
      break;
397
    case JSON_ERROR_STATE_MISMATCH:
398
      echo 'underflow of the modes mismatch';
399
      break;
400
    case JSON_ERROR_CTRL_CHAR:
401
      echo 'unexpected control character found';
402
      break;
403
    case JSON_ERROR_UTF8:
404
      echo 'malformed utf-8 characters';
405
      break;
406
    default:
407
      echo "unknown error";
408
      break;
409
  }
410
  echo "\n";
411
}
412
 
909 mateusz.vi 413
file_put_contents($repodir . '/_index.json', $json_blob);
562 mateuszvis 414
 
909 mateusz.vi 415
$cats_json = json_encode($cats);
416
file_put_contents($repodir . '/_cats.json', $cats_json);
417
 
562 mateuszvis 418
exit(0);
419
 
420
?>