Internet radio browser GUI for music/video streams from various directory services.

⌈⌋ ⎇ branch:  streamtuner2


Check-in [3f139e24c9]

Overview
Comment:Generates a "common-repo.json" list from specified files in a fossil repository. (Used with a glob param like "/repo.json/REPO/files/*.py" to slice out interesting meta information.)

primary origin: http://fossil.include-once.org/fossil-skins/wiki/features

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 3f139e24c9ae3414f8ab523282e2dd001fdeac2b
User & Date: mario on 2016-09-25 17:49:18
Other Links: manifest | tags
Context
2016-09-25
17:56
Minor text fixes. Compacted "configuration" back into features topic. check-in: cb5846cb2d user: mario tags: trunk
17:49
Generates a "common-repo.json" list from specified files in a fossil repository. (Used with a glob param like "/repo.json/REPO/files/*.py" to slice out interesting meta information.)

primary origin: http://fossil.include-once.org/fossil-skins/wiki/features check-in: 3f139e24c9 user: mario tags: trunk

2016-09-04
13:44
Prepare for crontab mode check-in: 7f2050ecba user: mario tags: trunk
Changes

Added dev/fossil-json-plugin-repo.php version [80abd12636].

































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<?php
/**
 * api: php
 * type: handler
 * title: Common.json repo list
 * description: Create json dict of selected fossil repository contents
 * version: 0.1
 * depends: fossil:json
 * doc: http://fossil.include-once.org/streamtuner2/wiki?name=plugin+meta+data
 *      https://pypi.python.org/pypi/pluginconf/
 *
 * Generates a „common-repo“-json list for files from a fossil repository.
 *
 *  · Used for streamtuner2 plugin manager - to refresh module list directly
 *    from repositories.
 *
 *  · Extracts key:value comments as seen above.
 *
 *  · Accepts PATH_INFO specifiers using glob patterns `/reponame/src*dir/*.ext`.
 *
 *  · You'll probably want to hook it beside the actual fossil server, using
 *    using a RewriteRule or ScriptAliasMatch.
 *    e.g.
 *        RewriteRule   ^(/?)repo.json/(.+)$ $1plugins.php/$2
 *
 *  · Each entry carries a faux $type, $dist and $file references, and all
 *    extracted meta fields, no docs. (= The crudest implementation so far.)
 *
 */


// run
$p = opts() and gen($p);



/**
 * Request params
 * [WHITELIST]
 *
 *  · assert alphanumeric repository.fossil name
 *  · limit allowed glob specifiers and file paths
 *
 */
function opts() {
    preg_match(
        "~^
           /(?<repo>[\w-]+)            # fossil basename
           /(?<glob>
              (?:
               [/\w.-]+                # basedir prefix
               [*]?                    
              ){0,3}                   # up to 3 *-glob segments
            )
        $~x",
        $_SERVER["PATH_INFO"], $groups
    );
    return $groups;
}


/**
 * Handler
 * [MAIN]
 *
 *  · invoked with $repo="projectname" and $glob="lib/*.src"
 *  · scans files, converts into repo.json, and outputs that
 *
 */
function gen($kwargs) {
    extract($kwargs);
    header("Content-Type: json/common-repo; x-ver=0");
    exit(
        json_encode(
            common_repo(
                query_files("/fossil.d/$repo.fossil", $glob),
                $repo
            ),
            JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT
        )
    );
}



/**
 * Read glob-specified files from repository
 * [EXEC]
 *
 *  · scans fossil repos for given filespec, e.g. "lib/plugins/*.py"
 *  · returns just first 2K from content
 *
 */
function query_files($R, $glob) {
   $r = [];
   if (!file_exists($R)) {
       return $r;
   }

   // loop through files
   $glob = escapeshellarg(preg_replace("~[^/\w.*-]~", "", $glob));
   $sql = escapeshellarg("
      SELECT  name AS name,  uuid,   SUBSTR(HEX(CONTENT(uuid)),1,2048) AS content
         FROM (SELECT  filename.name, bf.uuid, filename.fnid 
               FROM filename  JOIN mlink ON mlink.fnid=filename.fnid JOIN blob bf ON bf.rid=mlink.fid
                    JOIN event ON event.objid=mlink.mid
               WHERE (mlink.fnid NOT IN (SELECT fnid FROM mlink WHERE fid=0))
                     AND filename.name GLOB $glob
               GROUP BY filename.name
               ORDER BY event.mtime DESC
              )
   ");

   // Just retrieve as CSV list from fossil directly,
   // instead of using PDO handle and `fossil artifact` on each UUID
   $pipe = popen("fossil sqlite -R $R \".mode csv\" $sql", "rb");
   while ($row = fgetcsv($pipe, 0, ",", '"', '"')) {

      // skip emtpy rows
      if (count($row) != 3) {
          continue;
      }
      // add file
      $r[$row[0]] = hex2bin($row[2]);
   }

   return $r;
}


/**
 * Convert attributes into list
 * [TRANSFORM]
 *
 *  · from $fn=>$meta dict to [$pkg, $pkg] list
 *
 */
function common_repo($files, $R) {

    // extend each PMD key:value list
    $repo = meta_extract($files);
    foreach ($repo as $fn => $meta) {
    
        // basename, extension
        $id = strtok(basename($fn), ".");
        $ext = pathinfo($fn, PATHINFO_EXTENSION);
        $dir = basename(dirname($fn));
        
        // add some stub fields
        $meta += [
            "type" => "unknown",
            "api" => "$R",
            "title" => null,
            "description" => null,
            "version" => null,
        ];
        
        // common repo fields carry a `$` sigil
        $repo[$fn] = array_merge(
            [
                "\$name" => $id,                         # package basename
                "\$type" => "x-$ext",                    # e.g. "deb", "rpm" or "x-src"
                "\$dist" => "app/$R/$dir",               # e.g. "trusty/main" or "app:pkg:part"
                "\$file" => "http://fossil.include-once.org/$R/cat/$fn",   # resource locator
            ],
            $meta
        );
    }
    return array_values($repo);
}


/**
 * Extract plugin meta data
 * [REGEX]
 *
 *  · really just looks for scalar key:value lines
 *  · comment/docs are not extracted
 *
 */
function meta_extract($files) {
    foreach ($files as $fn=>$cnt) {
        preg_match_all("~\s*(?:#|//|[*])\s*(\w+):\h*(.+)~m", $cnt, $fields);
        $meta = array_combine($fields[1], $fields[2]);
        $meta = array_change_key_case($meta);
        $files[$fn] = $meta;
    }
    return $files;
}