Subversion Repositories SvarDOS

Rev

Rev 804 | Rev 912 | 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
 
909 mateusz.vi 13
  21 feb 2022: buildidx collects categories looking at the dir layout of each package
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
 
65
// reads file fil from zip archive z and returns its content, or false on error
66
function read_file_from_zip($z, $fil) {
67
  $zip = new ZipArchive;
68
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
69
    echo "ERROR: failed to open zip file '{$z}'\n";
70
    return(false);
71
  }
72
 
73
  // load the appinfo/pkgname.lsm file
74
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
75
 
76
  $zip->close();
77
  return($res);
78
}
79
 
80
 
731 mateusz.vi 81
function read_list_of_files_in_zip($z) {
82
  $zip = new ZipArchive;
83
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
84
    echo "ERROR: failed to open zip file '{$z}'\n";
85
    return(false);
86
  }
87
 
88
  $res = array();
89
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
90
 
91
  $zip->close();
92
  return($res);
93
}
94
 
95
 
562 mateuszvis 96
// reads a LSM string and returns it in the form of an array
97
function parse_lsm($s) {
98
  $res = array();
99
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
100
    // the line is "token: value", let's find the colon
101
    $colpos = strpos($l, ':');
102
    if (($colpos === false) || ($colpos === 0)) continue;
103
    $tok = strtolower(trim(substr($l, 0, $colpos)));
104
    $val = trim(substr($l, $colpos + 1));
105
    $res[$tok] = $val;
106
  }
107
  return($res);
108
}
109
 
110
 
731 mateusz.vi 111
// on PHP 8+ there is str_starts_with(), but not on PHP 7 so I use this
112
function str_head_is($haystack, $needle) {
113
  return strpos($haystack, $needle) === 0;
114
}
115
 
116
 
791 mateusz.vi 117
// returns an array that contains CORE packages (populated from the core subdirectory in pkgdir)
118
function load_core_list($repodir) {
119
  $res = array();
120
 
121
  foreach (scandir($repodir . '/core/') as $f) {
122
    if (!preg_match('/\.svp$/', $f)) continue;
123
    $res[] = explode('.', $f)[0];
124
  }
125
  return($res);
126
}
127
 
128
 
562 mateuszvis 129
// ***************** MAIN ROUTINE *********************************************
130
 
719 mateusz.vi 131
//echo "SvarDOS repository index generator ver {$PVER}\n";
562 mateuszvis 132
 
133
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
134
  echo "usage: php buildidx.php repodir\n";
135
  exit(1);
136
}
137
 
138
$repodir = $_SERVER['argv'][1];
139
 
140
$pkgfiles = scandir($repodir);
141
$pkgcount = 0;
142
 
738 mateusz.vi 143
 
795 mateusz.vi 144
// load the list of CORE and MSDOS_COMPAT packages
738 mateusz.vi 145
 
791 mateusz.vi 146
$core_packages_list = load_core_list($repodir);
804 bttr 147
$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 148
 
719 mateusz.vi 149
// do a list of all svp packages with their available versions and descriptions
562 mateuszvis 150
 
719 mateusz.vi 151
$pkgdb = array();
152
foreach ($pkgfiles as $fname) {
801 mateusz.vi 153
  if (!preg_match('/\.svp$/i', $fname)) continue; // skip non-svp files
562 mateuszvis 154
 
801 mateusz.vi 155
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
156
    echo "ERROR: {$fname} has a very weird name\n";
157
    continue;
158
  }
159
 
719 mateusz.vi 160
  $path_parts = pathinfo($fname);
161
  $pkgnam = explode('-', $path_parts['filename'])[0];
162
  $pkgfullpath = realpath($repodir . '/' . $fname);
562 mateuszvis 163
 
719 mateusz.vi 164
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
562 mateuszvis 165
  if ($lsm == false) {
802 mateusz.vi 166
    echo "ERROR: {$fname} does not contain an LSM file at the expected location\n";
719 mateusz.vi 167
    continue;
562 mateuszvis 168
  }
169
  $lsmarray = parse_lsm($lsm);
170
  if (empty($lsmarray['version'])) {
719 mateusz.vi 171
    echo "ERROR: lsm file in {$fname} does not contain a version\n";
172
    continue;
562 mateuszvis 173
  }
730 mateusz.vi 174
  if (strlen($lsmarray['version']) > 16) {
737 mateusz.vi 175
    echo "ERROR: version string in lsm file of {$fname} is too long (16 chars max)\n";
730 mateusz.vi 176
    continue;
177
  }
562 mateuszvis 178
  if (empty($lsmarray['description'])) {
719 mateusz.vi 179
    echo "ERROR: lsm file in {$fname} does not contain a description\n";
180
    continue;
562 mateuszvis 181
  }
182
 
731 mateusz.vi 183
  // validate the files present in the archive
184
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
739 mateusz.vi 185
  $pkgdir = $pkgnam;
186
 
768 mateusz.vi 187
  // special rule for "parent and children" packages
188
  if (str_head_is($pkgnam, 'djgpp_')) $pkgdir = 'djgpp'; // djgpp_* packages put their files in djgpp
754 mateusz.vi 189
  if ($pkgnam == 'fbc_help') $pkgdir = 'fbc'; // FreeBASIC help goes to the FreeBASIC dir
802 mateusz.vi 190
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
739 mateusz.vi 191
 
768 mateusz.vi 192
  // array used to detect duplicated entries after lower-case conversion
193
  $duparr = array();
194
 
909 mateusz.vi 195
  // will hold the list of categories that this package belongs to
196
  $catlist = array();
197
 
731 mateusz.vi 198
  foreach ($listoffiles as $f) {
199
    $f = strtolower($f);
768 mateusz.vi 200
    $path_array = explode('/', $f);
201
    // emit a warning when non-8+3 filenames are spotted and find duplicates
202
    foreach ($path_array as $item) {
203
      if (empty($item)) continue; // skip empty items at end of paths (eg. appinfo/)
204
      if (!preg_match("/[a-z0-9!#$%&'()@^_`{}~-]{1,8}(\.[a-z0-9!#$%&'()@^_`{}~-]{1,3}){0,1}/", $item)) {
205
        echo "WARNING: {$fname} contains a non-8+3 path (or weird char): {$item} (in $f)\n";
206
      }
207
    }
208
    // look for dups
209
    if (array_search($f, $duparr) !== false) {
210
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
211
    } else {
212
      $duparr[] = $f;
213
    }
731 mateusz.vi 214
    // LSM file is ok
215
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
216
    if ($f === "appinfo/") continue;
795 mateusz.vi 217
    // CORE and MSDOS_COMPAT packages are premium citizens and can do a little more
909 mateusz.vi 218
    $core_or_msdoscompat = 0;
219
    if (array_search($pkgnam, $core_packages_list) !== false) {
220
      $catlist[] = 'core';
221
      $core_or_msdoscompat = 1;
222
    }
223
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
224
      $catlist[] = 'msdos_compat';
225
      $core_or_msdoscompat = 1;
226
    }
227
    if ($core_or_msdoscompat == 1) {
736 mateusz.vi 228
      if (str_head_is($f, 'bin/')) continue;
779 mateusz.vi 229
      if (str_head_is($f, 'cpi/')) continue;
749 mateusz.vi 230
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
231
      if ($f === 'doc/') continue;
232
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
233
      if ($f === 'nls/') continue;
736 mateusz.vi 234
    }
798 mateusz.vi 235
    // the help package is allowed to put files in... help
236
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
909 mateusz.vi 237
    // must be category-prefixed file, add it to the list of categories for this package
238
    $catlist[] = explode('/', $f)[0];
749 mateusz.vi 239
    // well-known "category" dirs are okay
739 mateusz.vi 240
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
731 mateusz.vi 241
    if ($f === 'progs/') continue;
739 mateusz.vi 242
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
731 mateusz.vi 243
    if ($f === 'devel/') continue;
739 mateusz.vi 244
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
731 mateusz.vi 245
    if ($f === 'games/') continue;
739 mateusz.vi 246
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
731 mateusz.vi 247
    if ($f === 'drivers/') continue;
768 mateusz.vi 248
    echo "WARNING: {$fname} contains a file in an illegal location: {$f}\n";
731 mateusz.vi 249
  }
250
 
719 mateusz.vi 251
  $meta['fname'] = $fname;
252
  $meta['desc'] = $lsmarray['description'];
909 mateusz.vi 253
  $meta['cats'] = array_unique($catlist);
719 mateusz.vi 254
 
255
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
256
}
257
 
801 mateusz.vi 258
 
719 mateusz.vi 259
$db = array();
909 mateusz.vi 260
$cats = array();
719 mateusz.vi 261
 
909 mateusz.vi 262
// ******** compute the version-sorted list of packages with a single *********
263
// ******** description and category list for each package ********************
264
 
719 mateusz.vi 265
// iterate over each svp package
266
foreach ($pkgdb as $pkg => $versions) {
267
 
268
  // sort filenames by version, highest first
269
  uksort($versions, "version_compare");
270
  $versions = array_reverse($versions, true);
271
 
272
  foreach ($versions as $ver => $meta) {
273
    $fname = $meta['fname'];
274
    $desc = $meta['desc'];
275
 
276
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
277
 
278
    $meta2['ver'] = strval($ver);
279
    $meta2['bsum'] = $bsum;
280
 
281
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
909 mateusz.vi 282
    if (empty($db[$pkg]['cats'])) {
283
      $db[$pkg]['cats'] = $meta['cats'];
284
      $cats = array_unique(array_merge($cats, $meta['cats']));
285
    }
719 mateusz.vi 286
    $db[$pkg]['versions'][$fname] = $meta2;
287
  }
288
 
562 mateuszvis 289
  $pkgcount++;
290
 
291
}
292
 
719 mateusz.vi 293
if ($pkgcount < 100) echo "WARNING: an unexpectedly low number of packages has been found in the repo ({$pkgcount})\n";
562 mateuszvis 294
 
801 mateusz.vi 295
$json_blob = json_encode($db);
296
if ($json_blob === false) {
297
  echo "ERROR: JSON convertion failed! -> ";
298
  switch (json_last_error()) {
299
    case JSON_ERROR_DEPTH:
300
      echo 'maximum stack depth exceeded';
301
      break;
302
    case JSON_ERROR_STATE_MISMATCH:
303
      echo 'underflow of the modes mismatch';
304
      break;
305
    case JSON_ERROR_CTRL_CHAR:
306
      echo 'unexpected control character found';
307
      break;
308
    case JSON_ERROR_UTF8:
309
      echo 'malformed utf-8 characters';
310
      break;
311
    default:
312
      echo "unknown error";
313
      break;
314
  }
315
  echo "\n";
316
}
317
 
909 mateusz.vi 318
file_put_contents($repodir . '/_index.json', $json_blob);
562 mateuszvis 319
 
909 mateusz.vi 320
$cats_json = json_encode($cats);
321
file_put_contents($repodir . '/_cats.json', $cats_json);
322
 
562 mateuszvis 323
exit(0);
324
 
325
?>