Komentowana treść: AminetBrowser 1.0
[#1] Re: AminetBrowser 1.0
Super sprawa, tylko brakuje plików z opisami
[#2] Re: AminetBrowser 1.0
Przydałaby się też wyszukiwarka po nazwie, a może jest?
[#3] Re: AminetBrowser 1.0

@lukjan, post #1

po kliknięciu jest short w status bar jedynie

kod źródłowy dołączony, bardziej chodziło by na podstawie czegoś funkcjonalne go pokazać jak niewiele trzeba by zrobić taki program samemu

dzięki za newsa :)
4
[#4] Re: AminetBrowser 1.0
Bez szukajki, a dokładniej: pola od razu zawężającego listę do archiwów zawierających w nazwie wpisany tekst podczas pisania (search as you type), to jest to niewykorzystany potencjał.
3
[#5] Re: AminetBrowser 1.0

@Mario, post #4

Na pewno będzie nowa wersja, nie wytrzyma żeby jeszcze czegoś nie dodać szeroki uśmiech
5
[#6] Re: AminetBrowser 1.0

@Hellena, post #5

jest kod źródłowy, zachęcam do rozwinięcia :)

jak nikt nie zrobi z tym nic to pewnie coś dodam :)
1
[#7] Re: AminetBrowser 1.0

@juen, post #6

@Juen, wrzuć kod źródłowy na jakiś github/gitlab/codeberg, może łatwiej będzie zapanować nad zmianami (jeżeli ktokolwiek będzie chciał coś zmieniać, rozwijać).
[#8] Re: AminetBrowser 1.0

@Mario, post #4

No zdecydowanie nikt nie będzie przeglądać i szukać w gąszczu pożądanego programu.
Natomiast wyszukiwarka z możliwością pobrania zmieniłaby sytuację diametralnie, zamiast odpalać przeglądarkę, korzystałbym

Ostatnia aktualizacja: 14.04.2026 11:35:51 przez Jacques
[#9] Re: AminetBrowser 1.0

@Jacques, post #8

Też analogiczne narzędzie mogłoby być (to samo narzędzie tylko inne UI) podobnego do gnome:

apt-cache search picasso

itp.

OOo.

apt-get install

Też by się dało!
[#10] Re: AminetBrowser 1.0

@_DiskDoctor_, post #9

Byle nie w Imp3
[#11] Re: AminetBrowser 1.0

@_DiskDoctor_, post #9

takie cos juz na bank jest, chyba nawet juen kiedys coś podobnego próbował. Jestem pewien ze już to gdzieś widziałem.
2
[#12] Re: AminetBrowser 1.0

@juen, post #6

fajne. wez to wrzuć na githuba.
1
[#13] Re: AminetBrowser 1.0

@Mirq, post #11

3
[#14] Re: AminetBrowser 1.0

@Mirq, post #11

tak, to bylo w sumie spoko, juz nie dziala - https://aminet.net/package/util/misc/app
[#15] Re: AminetBrowser 1.0

@juen, post #14



zaktualizuje wersje na aminecie pewnie dzisiaj, mimo ze to example to jednak skoro sa prosby:
- dodano przycisk README w celu odczytu readme
- dodano input do szukania (w obrebie calego aminetu)

kod zrodlowy:

/*
 * aminet_browser.js - Aminet Archive Browser v1.2
 *
 * Browse and download files from http://aminet.net
 *
 *   - Cycle gadget      : select main category
 *   - Listview (top)    : subcategories (hardcoded)
 *   - Listview (middle) : file list from aminet.net
 *   - Double-click      : same row twice = Download
 *   - Download button   : ASL requester + http.get
 *   - Readme button     : fetch .readme, show in window
 *   - Search            : query aminet.net/search
 *   - Close window / Esc : close
 *
 * Run:  NodeAmiga examples/aminet_browser.js
 * Req:  bsdsocket.library (TCP/IP stack)
 */

var gui  = require('gui');
var gfx  = gui.gfx;
var http = require('http');
var fs   = require('fs');

/* ================================================================== */
/* Constants                                                           */
/* ================================================================== */

var AMINET = 'http://aminet.net';

var G_SUBCATS = {
    biz:  ['dbase', 'misc', 'spread'],
    comm: ['ambos', 'amiex', 'bbs', 'cnet', 'dlg', 'fido', 'irc',
           'mail', 'maxs', 'mebbs', 'misc', 'mmgr', 'net', 'news',
           'tcp', 'term', 'thor', 'ums', 'uucp', 'www', 'xeno', 'yam'],
    demo: ['40k', 'aga', 'bse', 'disk', 'ecs', 'euro', 'file', 'funet',
           'intro', 'mag', 'mega', 'misc', 'slide', 'sound', 'sp96',
           'ta95', 'ta96', 'ta97', 'tg93', 'tg95', 'tg96', 'tg97',
           'tp92', 'tp94', 'tp95', 'tp96', 'track'],
    dev:  ['amos', 'asm', 'basic', 'blitz', 'c', 'cross', 'debug', 'e',
           'gcc', 'gg', 'gui', 'hwood', 'lang', 'lib', 'm2', 'misc',
           'moni', 'mui', 'obero', 'src'],
    docs: ['anno', 'hard', 'help', 'hyper', 'lists', 'misc', 'rview'],
    game: ['2play', 'actio', 'board', 'data', 'demo', 'edit', 'gag',
           'hint', 'jump', 'misc', 'patch', 'race', 'role', 'shoot',
           'strat', 'text', 'think', 'wb'],
    gfx:  ['3d', 'arteff', 'conv', 'edit', 'fract', 'ifx', 'misc',
           'pbm', 'ppaint', 'show'],
    misc: ['amag', 'antiq', 'edu', 'emu', 'fish', 'imdb', 'kids',
           'math', 'misc', 'os', 'sci', 'unix', 'x11'],
    mods: ['32bit', '4mat', '8voic', 'airon', 'alpma', 'anakir', 'atmos',
           'aweso', 'bhead', 'bladr', 'blasa', 'blk', 'blkha', 'boing',
           'boray', 'casef', 'cato', 'cels', 'chart', 'chip', 'chrom',
           'chryl', 'clawz', 'cloud', 'crash', 'crn', 'csm', 'ctp',
           'cust', 'cutc', 'darde', 'dean', 'delor', 'demo', 'dizzy',
           'dlusn', 'doh', 'dremr', 'dsx', 'elbie', 'emax', 'ephnx',
           'essen', 'evrim', 'exprt', 'facet', 'fby', 'fermx', 'fox2',
           'funet', 'funk', 'grogo', 'hardc', 'hje', 'house', 'huezo',
           'hw', 'ijont', 'inst', 'instr', 'jazzc', 'jogei', 'jorma',
           'jungl', 'k4k', 'kaa', 'kicko', 'lead', 'mark', 'maxym',
           'med', 'melod', 'melom', 'midi', 'misc', 'mnc', 'moods',
           'mpg', 'mryo', 'mvp', 'ncase', 'neuro', 'nkn', 'nork',
           'ooze', 'otis', 'panik', 'pdy', 'pete', 'piano', 'pop',
           'pro', 'przk', 'punn', 'purg', 'racoo', 'rated', 'rock',
           'roz', 's3m', 'sbc', 'sets', 'sidew', 'slc', 'slow', 'smg',
           'smpl', 'sonor', 'spark', 'stame', 'stun', 'symph', 'synth',
           'techn', 'toady', 'tp96', 'tranc', 'uns', 'uprgh', 'vdo',
           'voice', 'voy', 'wmr', 'xceed', 'xm'],
    mus:  ['edit', 'midi', 'misc', 'play'],
    pix:  ['3dani', '3dobj', 'addic', 'anim', 'art', 'astro', 'back',
           'bill', 'boot', 'clip', 'eps', 'eric', 'fauna', 'fract',
           'gicon', 'glenn', 'guard', 'heiko', 'henz', 'icon', 'illu',
           'imagi', 'irc', 'jake', 'jason', 'map', 'mark', 'misc',
           'mpg', 'mwb', 'nevil', 'nicon', 'park', 'picon', 'real3',
           'sport', 'textu', 'theme', 'tp96', 'trace', 'unrl', 'vehic',
           'views', 'wb'],
    text: ['bfont', 'dtp', 'edit', 'font', 'fwrit', 'hyper', 'ifont',
           'misc', 'pfont', 'print', 'show', 'tex', 'tfont'],
    util: ['app', 'arc', 'batch', 'blank', 'boot', 'cdity', 'cli',
           'conv', 'crypt', 'dir', 'dopus', 'dtype', 'libs', 'misc',
           'moni', 'mouse', 'pack', 'rexx', 'shell', 'sys', 'time',
           'virus', 'wb']
};

var G_CATS = Object.keys(G_SUBCATS);

/* ================================================================== */
/* State                                                               */
/* ================================================================== */

var g_catIdx    = 0;    /* selected index in G_CATS             */
var g_subcats   = [];   /* current subcategory name strings     */
var g_subcatIdx = -1;   /* selected subcategory listview index  */
var g_files     = [];   /* [{name, size, date, desc}, ...]      */
var g_selFile   = -1;   /* index of selected file in g_files    */
var g_lastCode  = -1;   /* previous fileList evt.code (dbl-clk) */
var g_sortBy    = 0;    /* 0=name, 1=size, 2=date              */
var g_sortDir   = 0;    /* 0=ascending, 1=descending           */

var readmeWin   = null; /* readme viewer window (or null)       */
var g_rmLines   = [];   /* original readme lines (for hscroll)  */
var g_rmScroll  = 0;    /* horizontal scroll offset (chars)     */
var g_rmMaxLen  = 0;    /* longest line length (scroller total) */

/* ================================================================== */
/* Layout                                                              */
/* ================================================================== */

/* Get screen font metrics for proper layout */
var scr = gui.screenInfo();
var fw  = scr.fontWidth;    /* exact char width in pixels  */
var fh  = scr.fontHeight;   /* char height in pixels        */

var gadgets = [];
var gid = 1;
var rh  = fh + 4;  /* row height: font + padding */
var sp  = 4;        /* row spacing px */
var y   = sp;

/* Row 1: Category / Sort / Order cycles */
gadgets.push({ kind: 'cycle', id: gid++,
    left: '1%', top: y, width: '50%', height: rh,
    items: G_CATS, value: 0 });
var ID_CAT = 1;

gadgets.push({ kind: 'cycle', id: gid++,
    left: '53%', top: y, width: '25%', height: rh,
    items: ['name', 'size', 'date'], value: 0 });
var ID_SORT = 2;

gadgets.push({ kind: 'cycle', id: gid++,
    left: '80%', top: y, width: '19%', height: rh,
    items: ['asc', 'desc'], value: 0 });
var ID_ORDER = 3;
y += rh + sp;

/* Bottom area: status + search + buttons = rh*3 + sp*3 */
var btmH   = rh * 3 + sp * 3;
var winH   = rh * 16 + btmH + sp;
var listH  = winH - y - btmH - 2;

/* Subcategory listview — 6 chars + scrollbar (16px) + borders (8px) */
var subW = fw * 6 + 24;
gadgets.push({ kind: 'listview', id: gid++,
    left: 4, top: y, width: subW, height: listH, flex: true,
    items: [], value: 0 });
var ID_SUBCAT = 4;

/* File listview — fills remaining width */
var fileL = subW + 8;
gadgets.push({ kind: 'listview', id: gid++,
    left: fileL, top: y, width: -(fileL + 4), height: listH, flex: true,
    items: ['Select a subcategory...'], value: 0 });
var ID_FILES = 5;

/* Status text — top of bottom area */
gadgets.push({ kind: 'text', id: gid++,
    left: '1%', top: -(rh * 3 + sp * 3), width: '98%', height: rh,
    value: 'Ready -- pick a category.' });
var ID_STATUS = 6;

/* Search string — middle row of bottom area */
var searchY = -(rh * 2 + sp * 2);
gadgets.push({ kind: 'string', id: gid++,
    label: 'Find:',
    left: '12%', top: searchY, width: '87%', height: rh,
    value: '' });
var ID_SEARCH_STR = 7;

/* Bottom row: buttons */
var btnY = -(rh + sp);

gadgets.push({ kind: 'button', id: gid++, label: 'Download',
    left: '1%', top: btnY, width: '32%', height: rh });
var ID_DOWNLOAD = 8;

gadgets.push({ kind: 'button', id: gid++, label: 'Readme',
    left: '34%', top: btnY, width: '32%', height: rh });
var ID_README = 9;

gadgets.push({ kind: 'button', id: gid++,
    label: 'Search All',
    left: '67%', top: btnY, width: '32%', height: rh });
var ID_SEARCH = 10;

/* ================================================================== */
/* Create window                                                       */
/* ================================================================== */

var win = gui.createWindow({
    title:     'Aminet Browser 1.2 - NodeAmiga by Juen',
    width:     520,
    height:    winH,
    left:      20,
    top:       15,
    resizable: true,
    minWidth:  300,
    minHeight: 140,
    gadgets:   gadgets
});

/* Download and Readme start disabled (no file selected) */
gui.setDisabled(win, ID_DOWNLOAD, true);
gui.setDisabled(win, ID_README, true);

/* ================================================================== */
/* Utility helpers                                                     */
/* ================================================================== */

function setStatus(msg) {
    gui.set(win, ID_STATUS, msg);
}

/* Enable/disable Download and Readme based on g_selFile */
function updateFileButtons() {
    var dis = (g_selFile < 0);
    gui.setDisabled(win, ID_DOWNLOAD, dis);
    gui.setDisabled(win, ID_README, dis);
}

/* Disable or enable all interactive gadgets.
 * NOTE: ID_SEARCH_STR (string gadget) is excluded
 * because GA_Disabled on STRING_KIND can hang. */
function setAllDisabled(flag) {
    gui.setDisabled(win, ID_CAT, flag);
    gui.setDisabled(win, ID_SORT, flag);
    gui.setDisabled(win, ID_ORDER, flag);
    gui.setDisabled(win, ID_SUBCAT, flag);
    gui.setDisabled(win, ID_FILES, flag);
    gui.setDisabled(win, ID_DOWNLOAD, flag);
    gui.setDisabled(win, ID_README, flag);
    gui.setDisabled(win, ID_SEARCH, flag);
}

/* Scan html for all substrings between literal before/after markers */
function extractBetween(html, before, after) {
    var results = [];
    var pos = 0;
    var s, e;
    while (true) {
        s = html.indexOf(before, pos);
        if (s === -1) break;
        s += before.length;
        e = html.indexOf(after, s);
        if (e === -1) break;
        results.push(html.substring(s, e));
        pos = e + after.length;
    }
    return results;
}

/* Strip all HTML tags from a string */
function stripTags(s) {
    var out = '', inTag = false, i, c;
    for (i = 0; i < s.length; i++) {
        c = s.charAt(i);
        if      (c === '<') { inTag = true;  }
        else if (c === '>') { inTag = false; }
        else if (!inTag)    { out  += c;     }
    }
    return out;
}

/* Trim leading/trailing whitespace */
function trimStr(s) {
    return s.replace(/^\s+|\s+$/g, '');
}

/* Pad/truncate string to exactly w characters for column alignment */
function col(s, w) {
    s = String(s);
    while (s.length < w) { s += ' '; }
    return s.length > w ? s.substring(0, w) : s;
}

/* Strip tag-attribute prefix "...>" from a raw <td chunk, then stripTags */
function tdText(raw) {
    var gt = raw.indexOf('>');
    return trimStr(stripTags(gt >= 0 ? raw.substring(gt + 1) : raw));
}

/* ================================================================== */
/* Readme viewer window                                                */
/* ================================================================== */

function closeReadmeWin() {
    if (readmeWin) {
        gui.closeWindow(readmeWin);
        readmeWin = null;
    }
}

/* Shift all readme lines by g_rmScroll and update listview */
function updateReadmeView() {
    var shifted = [];
    var i, ln;
    if (!readmeWin) return;
    for (i = 0; i < g_rmLines.length; i++) {
        ln = g_rmLines[i];
        if (g_rmScroll > 0 && ln.length > g_rmScroll) {
            shifted.push(ln.substring(g_rmScroll));
        } else if (g_rmScroll > 0) {
            shifted.push('');
        } else {
            shifted.push(ln);
        }
    }
    gui.set(readmeWin, 1, shifted);
}

function doReadme() {
    var fi, dot, base, readmeName, pathBase, url;
    var res, text, maxLen, maxCols, winW, i;

    if (g_selFile < 0 || g_selFile >= g_files.length) {
        setStatus('Select a file first');
        return;
    }

    fi = g_files[g_selFile];

    /* Replace extension with .readme */
    dot = fi.name.lastIndexOf('.');
    base = (dot > 0) ? fi.name.substring(0, dot) : fi.name;
    readmeName = base + '.readme';

    /* Build URL from fi.path */
    dot = fi.path.lastIndexOf('.');
    pathBase = (dot > 0) ? fi.path.substring(0, dot) : fi.path;
    url = AMINET + '/' + pathBase + '.readme';

    setStatus('Fetching ' + readmeName + '...');
    setAllDisabled(true);

    try {
        res = http.get(url);
        if (!res || !res.body || res.body.length === 0) {
            /* Fallback: strip one more extension
             * e.g. file.tar.gz -> file.tar.readme
             * failed, now try file.readme */
            dot = pathBase.lastIndexOf('.');
            if (dot > 0) {
                pathBase = pathBase.substring(0, dot);
                url = AMINET + '/' +
                      pathBase + '.readme';
                res = http.get(url);
            }
        }

        if (!res || !res.body || res.body.length === 0) {
            setStatus('No readme for ' + fi.name);
            setAllDisabled(false);
            updateFileButtons();
            return;
        }

        /* Split into lines, strip CR */
        text = String(res.body).replace(/\r/g, '');
        g_rmLines  = text.split('\n');
        g_rmScroll = 0;

        /* Find longest line for scroller range */
        g_rmMaxLen = 0;
        for (i = 0; i < g_rmLines.length; i++) {
            if (g_rmLines[i].length > g_rmMaxLen)
                g_rmMaxLen = g_rmLines[i].length;
        }

        winW    = 560;
        maxCols = Math.floor((winW - 30) / fw);

        /* Close previous readme window */
        closeReadmeWin();

        /* Open readme window: listview + h-scroller */
        readmeWin = gui.createWindow({
            title:     readmeName,
            width:     winW,
            height:    300,
            left:      60,
            top:       30,
            resizable: true,
            minWidth:  200,
            minHeight: 100,
            gadgets: [
                { kind: 'listview', id: 1,
                  left: 0, top: 0,
                  width: '100%', height: -(rh + sp),
                  flex: true,
                  items: g_rmLines },
                { kind: 'scroller', id: 2,
                  left: 0, top: -(rh),
                  width: '100%', height: rh,
                  min: 0, max: g_rmMaxLen,
                  value: 0, visible: maxCols }
            ]
        });

        setStatus('Readme: ' + readmeName +
                  ' (' + g_rmLines.length + ' lines)');
    } catch (ex) {
        setStatus('Readme error: ' +
                  (ex.message || String(ex)));
    }

    setAllDisabled(false);
    updateFileButtons();
}

/* ================================================================== */
/* Display subcategories for a category (from hardcoded G_SUBCATS)     */
/* ================================================================== */

function loadSubcats(cat) {
    g_subcats   = G_SUBCATS[cat] || [];
    g_subcatIdx = -1;
    g_files     = [];
    g_selFile   = -1;
    g_lastCode  = -1;
    gui.set(win, ID_FILES,  ['Select a subcategory...']);
    updateFileButtons();

    if (g_subcats.length === 0) {
        gui.set(win, ID_SUBCAT, []);
        setStatus('No subcategories for ' + cat);
    } else {
        gui.set(win, ID_SUBCAT, g_subcats);
        setStatus(cat + ': ' + g_subcats.length +
                  ' subcategories');
    }
}

/* ================================================================== */
/* Sorting and display helpers                                         */
/* ================================================================== */

/* Parse size string like "123K", "1.2M" to bytes for comparison */
function parseSizeKB(s) {
    var n, last;
    if (!s || s.length === 0) return 0;
    last = s.charAt(s.length - 1);
    n = parseFloat(s);
    if (isNaN(n)) return 0;
    if (last === 'M' || last === 'm') return n * 1024;
    if (last === 'G' || last === 'g') return n * 1024 * 1024;
    return n;  /* already K or plain number */
}

/* Sort g_files in-place according to g_sortBy / g_sortDir */
function sortFiles() {
    /* Note: g_files.sort(compareFiles) produces undefined values
     * due to engine bug — using manual sort as workaround */
    var i, j, tmp;
    for (i = 1; i < g_files.length; i++) {
        tmp = g_files[i];
        j = i - 1;
        while (j >= 0 && compareFiles(g_files[j], tmp) > 0) {
            g_files[j + 1] = g_files[j];
            j--;
        }
        g_files[j + 1] = tmp;
    }
}

function compareFiles(a, b) {
    var cmp = 0;
    if (g_sortBy === 0) {
        /* name */
        cmp = a.name < b.name ? -1 : (a.name > b.name ? 1 : 0);
    } else if (g_sortBy === 1) {
        /* size */
        cmp = parseSizeKB(a.size) - parseSizeKB(b.size);
    } else {
        /* date — string comparison works for YYYY-MM-DD format */
        cmp = a.date < b.date ? -1 : (a.date > b.date ? 1 : 0);
    }
    return g_sortDir ? -cmp : cmp;
}

/* Rebuild display list from g_files and update listview */
function refreshFileList() {
    var displayList = [];
    var i, f;
    for (i = 0; i < g_files.length; i++) {
        f = g_files[i];
        displayList.push(col(f.name, 28) + '  ' +
                         col(f.size, 5) + '  ' + f.date);
    }
    g_selFile  = -1;
    g_lastCode = -1;
    updateFileButtons();
    if (displayList.length === 0) {
        gui.set(win, ID_FILES, ['(empty)']);
    } else {
        gui.set(win, ID_FILES, displayList);
    }
}

/* ================================================================== */
/* Fetch and display file listing for category/subcategory             */
/* ================================================================== */

/*
 * Parse file rows from an HTML page and append to g_files.
 * Returns the number of new entries found.
 */
function parseRows(html, cat, sub) {
    var linkPat, pos, rs, re, row, ls, le;
    var name, size, date, desc;
    var tds, i, t, count;

    linkPat = 'href="/' + cat + '/' + sub + '/';
    pos     = 0;
    count   = 0;

    while (true) {
        rs = html.indexOf('<tr', pos);
        if (rs === -1) break;
        re = html.indexOf('</tr>', rs);
        if (re === -1) break;
        row = html.substring(rs, re + 5);
        pos = re + 5;

        /* Does this row contain a file download link? */
        ls = row.indexOf(linkPat);
        if (ls === -1) continue;
        ls += linkPat.length;
        le  = row.indexOf('"', ls);
        if (le === -1) continue;
        name = row.substring(ls, le);
        /* Reject empty names or sub-paths */
        if (name.length === 0 || name.indexOf('/') !== -1) { continue; }

        try {
            /* Extract <td> cells */
            tds  = extractBetween(row, '<td', '</td>');
            size = ''; date = ''; desc = '';
            for (i = 0; i < tds.length; i++) {
                t = tdText(tds[i]);
                if      (i === 4) { size = t; }
                else if (i === 5) { date = t; }
                else if (i === 7) { desc = t; }
            }
        } catch (ex2) {
            /* If cell parsing fails, use defaults */
            console.log('parse warning: ' + name +
                        ': ' + String(ex2));
            size = ''; date = ''; desc = '';
        }

        g_files.push({ name: name,
                        path: cat + '/' + sub + '/' + name,
                        size: size, date: date, desc: desc });
        count++;
    }
    return count;
}

/*
 * Detect whether aminet HTML contains a link to the next page.
 * Returns the next page number, or -1 if no more pages.
 */
function findNextPage(html, currentPage) {
    var needle = 'page=' + (currentPage + 1);
    if (html.indexOf(needle) !== -1) {
        return currentPage + 1;
    }
    return -1;
}

/* Map g_sortBy (0=name,1=size,2=date) to aminet sort parameter */
function aminetSortParam() {
    if (g_sortBy === 1) return 'size';
    if (g_sortBy === 2) return 'date';
    return 'name';
}

/* Map g_sortDir (0=asc,1=desc) to aminet ord parameter */
function aminetOrdParam() {
    return g_sortDir ? 'DESC' : 'ASC';
}

function loadFiles(cat, sub) {
    var baseUrl, url, res, html, errMsg;
    var page, nextPage, pageCount;

    setStatus('Loading ' + cat + '/' + sub + '...');
    g_files    = [];
    g_selFile  = -1;
    g_lastCode = -1;
    gui.set(win, ID_FILES, ['Loading...']);

    /* Disable all gadgets during network fetch */
    setAllDisabled(true);

    baseUrl = AMINET + '/' + cat + '/' + sub;
    page    = 1;   /* aminet pages are 1-based */

    try {
        while (true) {
            url = baseUrl + '?sort=' + aminetSortParam() +
                  '&ord=' + aminetOrdParam() +
                  '&page=' + page;

            if (page > 1) {
                setStatus('Loading ' + cat + '/' + sub +
                          ' (page ' + page + ')...');
            }

            res = http.get(url);
            if (!res || !res.body) {
                if (page === 1) {
                    setStatus('No response from aminet.net');
                    gui.set(win, ID_FILES,
                            ['(no response)']);
                    setAllDisabled(false);
                    updateFileButtons();
                    return;
                }
                break;  /* no more pages */
            }
            html = String(res.body);

            pageCount = parseRows(html, cat, sub);

            /* If this page yielded no files, we're done */
            if (pageCount === 0) {
                break;
            }

            /* Check if there is a next page link */
            nextPage = findNextPage(html, page);
            if (nextPage === -1) {
                break;
            }
            page = nextPage;
        }
    } catch (ex) {
        errMsg = ex && ex.message ? ex.message : String(ex);
        console.log('loadFiles fetch error: ' + errMsg);
        if (g_files.length === 0) {
            setStatus('Fetch error: ' + errMsg);
            gui.set(win, ID_FILES,
                    ['Fetch error: ' + errMsg]);
            setAllDisabled(false);
            updateFileButtons();
            return;
        }
        /* Partial results — show what we got */
    }

    /* Re-enable all gadgets */
    setAllDisabled(false);
    updateFileButtons();

    if (g_files.length === 0) {
        setStatus('No files in ' + cat + '/' + sub);
        gui.set(win, ID_FILES, ['(empty)']);
    } else {
        sortFiles();
        refreshFileList();
        setStatus(cat + '/' + sub + ': ' +
                  g_files.length + ' files' +
                  (page > 1 ? ' (' + page + ' pages)' : ''));
    }
}

/* ================================================================== */
/* Download selected file — ASL file requester for destination         */
/* ================================================================== */

function doDownload() {
    var fi, url, dest, res;

    if (g_selFile < 0 || g_selFile >= g_files.length) {
        setStatus('Select a file from the list first');
        return;
    }

    fi  = g_files[g_selFile];
    url = AMINET + '/' + fi.path;

    /* Ask user where to save */
    dest = gui.fileRequest({
        title:  'Save: ' + fi.name,
        save:   true,
        drawer: 'RAM:',
        file:   fi.name
    });
    if (!dest) {
        setStatus('Download cancelled');
        return;
    }

    /* Disable all gadgets during download */
    setAllDisabled(true);

    setStatus('Downloading ' + fi.name + ' ...');
    try {
        res = http.get(url);
        if (!res || !res.body) {
            setStatus('Download failed: empty response');
            return;
        }
        fs.writeFileSync(dest.path, res.body);
        setStatus('Saved ' + fi.name + ' (' +
                  res.body.length + ' B)  \xbb ' +
                  dest.path);
    } catch (ex) {
        setStatus('Download error: ' +
                  (ex.message || String(ex)));
    } finally {
        /* Re-enable all gadgets */
        setAllDisabled(false);
        updateFileButtons();
    }
}


/* ================================================================== */
/* Search aminet.net                                                   */
/* ================================================================== */

/*
 * Parse search result rows from aminet search HTML.
 * Search results have links like href="/cat/sub/filename".
 * Returns count of entries appended to g_files.
 */
function parseSearchRows(html) {
    var pos, rs, re, row, ls, le, href;
    var name, size, date, desc, path;
    var tds, i, t, count, parts;

    pos   = 0;
    count = 0;

    while (true) {
        rs = html.indexOf('<tr', pos);
        if (rs === -1) break;
        re = html.indexOf('</tr>', rs);
        if (re === -1) break;
        row = html.substring(rs, re + 5);
        pos = re + 5;

        /* Look for file download links like href="/cat/sub/file" */
        ls = row.indexOf('href="/');
        if (ls === -1) continue;
        ls += 7;   /* skip href="/ */
        le = row.indexOf('"', ls);
        if (le === -1) continue;
        href = row.substring(ls, le);

        /* Must have exactly 2 slashes: cat/sub/file */
        parts = href.split('/');
        if (parts.length !== 3) continue;
        if (parts[2].length === 0) continue;
        /* Skip non-file links (dirs, pages, etc.) */
        if (parts[2].indexOf('.') === -1) continue;

        name = parts[2];
        path = href;

        try {
            tds  = extractBetween(row, '<td', '</td>');
            size = ''; date = ''; desc = '';
            for (i = 0; i < tds.length; i++) {
                t = tdText(tds[i]);
                if      (i === 4) { size = t; }
                else if (i === 5) { date = t; }
                else if (i === 7) { desc = t; }
            }
        } catch (ex2) {
            size = ''; date = ''; desc = '';
        }

        g_files.push({ name: name, path: path,
                        size: size, date: date, desc: desc });
        count++;
    }
    return count;
}

function doSearch() {
    var query, url, res, html, count;

    /* Read search term from string gadget */
    query = trimStr(gui.get(win, ID_SEARCH_STR) || '');
    if (query.length === 0) {
        setStatus('Type a search term in Find');
        return;
    }

    setStatus('Searching ALL of Aminet: ' +
              query + '...');
    g_files    = [];
    g_selFile  = -1;
    g_lastCode = -1;
    gui.set(win, ID_FILES, ['Searching...']);
    setAllDisabled(true);

    try {
        url = AMINET + '/search?query=' + query;
        res = http.get(url);
        if (!res || !res.body) {
            setStatus('No response from aminet.net');
            gui.set(win, ID_FILES, ['(no response)']);
            setAllDisabled(false);
            updateFileButtons();
            return;
        }
        html  = String(res.body);
        count = parseSearchRows(html);
    } catch (ex) {
        setStatus('Search error: ' +
                  (ex.message || String(ex)));
        gui.set(win, ID_FILES,
                ['Search error']);
        setAllDisabled(false);
        updateFileButtons();
        return;
    }

    setAllDisabled(false);
    updateFileButtons();

    if (g_files.length === 0) {
        setStatus('No results for: ' + query);
        gui.set(win, ID_FILES, ['(no results)']);
    } else {
        sortFiles();
        refreshFileList();
        setStatus('Found ' + g_files.length +
                  ' in all Aminet for: ' + query);
    }
}

/* ================================================================== */
/* Startup — display subcategories for first category                  */
/* ================================================================== */

loadSubcats(G_CATS[g_catIdx]);

/* ================================================================== */
/* Main event loop (polls both windows via waitTOF)                    */
/* ================================================================== */

/* Process a single main-window event; returns false to quit */
function handleEvent(evt) {
    if (evt.type === 'close') return false;

    if (evt.type === 'key' && evt.key === '\x1b') return false;

    if (evt.type === 'gadgetup') {

        if (evt.id === ID_DOWNLOAD) {
            doDownload();

        } else if (evt.id === ID_README) {
            doReadme();

        } else if (evt.id === ID_SEARCH) {
            doSearch();

        } else if (evt.id === ID_SEARCH_STR) {
            /* Enter pressed in search field */
            doSearch();

        } else if (evt.id === ID_CAT) {
            g_catIdx = evt.code;
            loadSubcats(G_CATS[g_catIdx]);

        } else if (evt.id === ID_SORT) {
            g_sortBy = evt.code;
            if (g_files.length > 0) {
                setAllDisabled(true);
                sortFiles();
                refreshFileList();
                setAllDisabled(false);
                updateFileButtons();
            }

        } else if (evt.id === ID_ORDER) {
            g_sortDir = evt.code;
            if (g_files.length > 0) {
                setAllDisabled(true);
                sortFiles();
                refreshFileList();
                setAllDisabled(false);
                updateFileButtons();
            }

        } else if (evt.id === ID_SUBCAT) {
            g_subcatIdx = evt.code;
            g_lastCode  = -1;
            if (g_subcatIdx >= 0 &&
                g_subcatIdx < g_subcats.length) {
                loadFiles(G_CATS[g_catIdx],
                          g_subcats[g_subcatIdx]);
            }

        } else if (evt.id === ID_FILES) {
            if (evt.code === g_lastCode) {
                g_selFile  = evt.code;
                g_lastCode = -1;
                doDownload();
            } else {
                g_selFile  = evt.code;
                g_lastCode = evt.code;
                updateFileButtons();
                if (g_selFile >= 0 &&
                    g_selFile < g_files.length) {
                    var finfo = g_files[g_selFile];
                    var info  = finfo.name;
                    if (finfo.size) {
                        info += '   ' + finfo.size;
                    }
                    if (finfo.desc) {
                        info += '   ' + finfo.desc;
                    }
                    setStatus(info);
                }
            }
        }
    }
    return true;
}

var running = true;
while (running) {
    var evt, revt;

    if (readmeWin) {
        /* Both windows open — poll both, sleep on TOF */
        evt  = gui.pollEvent(win);
        revt = gui.pollEvent(readmeWin);

        if (revt) {
            if (revt.type === 'close') {
                closeReadmeWin();
            } else if (revt.type === 'resize') {
                /* Recalculate visible chars for new width */
                var newCols = Math.floor(
                    (revt.width - 30) / fw);
                gui.set(readmeWin, 2,
                    { visible: newCols,
                      total: g_rmMaxLen });
            } else if (revt.id === 2) {
                g_rmScroll = gui.get(readmeWin, 2);
                updateReadmeView();
            }
        }

        if (!evt) {
            gfx.waitTOF();
            continue;
        }
    } else {
        /* Only main window — block efficiently */
        evt = gui.waitEvent(win);
        if (!evt) break;
    }

    if (!handleEvent(evt)) {
        running = false;
    }
}

closeReadmeWin();
gui.closeWindow(win);


Ostatnia aktualizacja: 15.04.2026 12:04:00 przez juen
Na stronie www.PPA.pl, podobnie jak na wielu innych stronach internetowych, wykorzystywane są tzw. cookies (ciasteczka). Służą ona m.in. do tego, aby zalogować się na swoje konto, czy brać udział w ankietach. Ze względu na nowe regulacje prawne jesteśmy zobowiązani do poinformowania Cię o tym w wyraźniejszy niż dotychczas sposób. Dalsze korzystanie z naszej strony bez zmiany ustawień przeglądarki internetowej będzie oznaczać, że zgadzasz się na ich wykorzystywanie.
OK, rozumiem