-*- mode: org; coding: utf-8; -*- #+TITLE: What's left to do? #+STARTUP: content hidestars Copyright © 2012, 2013, 2014 Ludovic Courtès Copyright © 2019 Mathieu Othacehe Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. * MAYBE Add a substituter that uses the GNUnet DHT or [[http://libswift.org][libswift]] Would be neat if binaries could be pushed to and pulled from the GNUnet DHT or rather libswift (since DHTs aren’t suited for large payloads). Guix users would sign their binaries, and define which binaries they trust. Use UPnP and similar to traverse NAT, like ‘filegive’ does. * user interface ** add guile-ncurses interface * extend ** add ‘recommends’ field For instance, glibc, binutils, gcc, and ld-wrapper would recommend each other. ‘guix package -i’ could ask interactively (?), or allow users to follow all or none of the recommendations. ** add a ‘user-environment-hook’ This should specify builder code to be run when building a user environment with ‘guix-package’. For instance, Texinfo’s hook would create a new ‘dir’. ** extend ‘propagated-build-inputs’ with support for multiple outputs #+BEGIN_SRC scheme (outputs '("out" "include")) (propagated-build-inputs `(((("i1" ,p1 "o1") ("i2" ,p2)) => "include") ("i3" ,p3))) #+END_SRC * synchronize non-GNU package descriptions with the [[http://directory.fsf.org][FSD]] Meta-data for GNU packages, including descriptions and synopses, can be dumped from the FSD: http://directory.fsf.org/wiki?title=GNU/Export&action=purge . We could periodically synchronize with that. * add a guildhall build system The Guildhall is Guile’s packaging system. It should be easy to add a ‘guildhall-build-system’ that does the right thing based on guildhall recipes. * union Support sophisticated collision handling when building a union: honor per-package priorities, etc. * add GUIX_ALLOW_EXPENSIVE_TESTS Tests that need to download stuff or otherwise take a long time would only be run when that is defined. * guix build utils ** MAYBE Change ‘ld-wrapper’ to add RPATH for libs passed by file name ** MAYBE Add equivalent to chrpath that uses [[https://gitorious.org/guile-dlhacks/guile-dlhacks/][guile-dlhacks]] ** MAYBE Add a hash-rewriting thing for deep dependency replacement without rebuild See [[https://github.com/NixOS/nixpkgs/commit/d1662d715514e6ef9d3dc29f132f1b3d8e608a18][Shea Levy's `replace-dependency' in Nixpkgs]]. * distro ** port to GNU/Hurd, aka. ‘i686-gnu’ Problems include that current glibc releases do not build on GNU/Hurd. In addition, there haven’t been stable releases of GNU Mach, MiG, and Hurd, which would be a pre-condition. * Installer ** Fix impossibility to restart on error after cow-store has been started See https://lists.gnu.org/archive/html/guix-devel/2018-12/msg00161.html. - Force reboot upon installer failure - Unshare the installer process - Run the installer process in a separate namespace ** Partitioning *** Add RAID support *** Add more partitioning schemes The actual schemes are taken from Debian Installer but some are not implemented yet: like "Separate partitions for /home /var and /tmp". *** Replace wait page "Partition formating is in progress, please wait" Create a new waiting page describing what's being done: [ 20% ] Running mkfs.ext4 on /dev/sda2 ... [ 40% ] Running mkfs.ext4 on /dev/sda3 ... *** Add a confirmation page before formating/partitioning ** Desktop environments *** Allow for no desktop environments Propose to choose between "headless server" and "lightweight X11" in a new page. *** Add services selection feature Add a services page to the configuration. Ask for services to be installed like SSH, bluetooth, TLP in a checkbox list? ** Locale and keymap *** Try to guess user locale and keymap by probing BIOS or HW (dmidecode) ** Timezone *** Regroup everything in one single page Under the form: (UTC + 1) Europe/Paris (UTC + 2) Africa/Cairo ... ** Display issue *** Investigate display issue described here: https://lists.gnu.org/archive/html/guix-devel/2019-01/msg00305.html href='#n33'>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 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
"use strict";

const api = require("../api.js");
const router = require("../router.js");
const uri = require("../util/uri.js");
const misc = require("../util/misc.js");
const progress = require("../util/progress.js");
const settings = require("../models/settings.js");
const topNavigation = require("../models/top_navigation.js");
const Post = require("../models/post.js");
const Tag = require("../models/tag.js");
const PostUploadView = require("../views/post_upload_view.js");
const EmptyView = require("../views/empty_view.js");

const genericErrorMessage =
    "One of the posts needs your attention; " +
    'click "resume upload" when you\'re ready.';

class PostUploadController {
    constructor() {
        this._lastCancellablePromise = null;

        if (!api.hasPrivilege("posts:create")) {
            this._view = new EmptyView();
            this._view.showError("You don't have privileges to upload posts.");
            return;
        }

        topNavigation.activate("upload");
        topNavigation.setTitle("Upload");
        this._view = new PostUploadView({
            canUploadAnonymously: api.hasPrivilege("posts:create:anonymous"),
            canViewPosts: api.hasPrivilege("posts:view"),
            enableSafety: api.safetyEnabled(),
            defaultSafety: settings.get().uploadSafety
        });
        this._view.addEventListener("change", (e) => this._evtChange(e));
        this._view.addEventListener("submit", (e) => this._evtSubmit(e));
        this._view.addEventListener("cancel", (e) => this._evtCancel(e));
    }

    _evtChange(e) {
        if (e.detail.uploadables.length) {
            misc.enableExitConfirmation();
        } else {
            misc.disableExitConfirmation();
            this._view.clearMessages();
        }
    }

    _evtCancel(e) {
        if (this._lastCancellablePromise) {
            this._lastCancellablePromise.abort();
        }
    }

    _evtSubmit(e) {
        this._view.disableForm();
        this._view.clearMessages();
        const tagErrors = []; // to be displayed after all uploads

        e.detail.uploadables
            .reduce(
                (promise, uploadable) =>
                    promise.then(() =>
                        this._uploadSinglePost(
                            uploadable,
                            e.detail.skipDuplicates,
                            e.detail.copyTagsToOriginals
                        )
                    ),
                Promise.resolve()
            )
            .then(
                () => {
                    this._view.clearMessages();
                    misc.disableExitConfirmation();
                    const ctx = router.show(uri.formatClientLink("posts"));
                    ctx.controller.showSuccess("Posts uploaded.");
                    for (let tagError of tagErrors) {
                        ctx.controller.showError(tagError);
                    }
                },
                (error) => {
                    if (error.uploadable) {
                        if (error.similarPosts) {
                            error.uploadable.lookalikes = error.similarPosts;
                            this._view.updateUploadable(error.uploadable);
                            this._view.showInfo(genericErrorMessage);
                            this._view.showInfo(
                                error.message,
                                error.uploadable
                            );
                        } else {
                            this._view.showError(genericErrorMessage);
                            this._view.showError(
                                error.message,
                                error.uploadable
                            );
                        }
                    } else {
                        this._view.showError(error.message);
                    }
                    this._view.enableForm();
                }
            );
    }

    _uploadSinglePost(uploadable, skipDuplicates, copyTagsToOriginals) {
        progress.start();
        let reverseSearchPromise = Promise.resolve();
        if (!uploadable.lookalikesConfirmed) {
            reverseSearchPromise = Post.reverseSearch(
                uploadable.url || uploadable.file
            );
        }
        this._lastCancellablePromise = reverseSearchPromise;

        return reverseSearchPromise
            .then((searchResult) => {
                if (searchResult) {
                    // notify about exact duplicate
                    if (searchResult.exactPost) {
                        if (copyTagsToOriginals) {
                            return this._copyTagsToOriginalAndSave(
                                uploadable, searchResult.exactPost
                            );
                        } else if (skipDuplicates) {
                            this._view.removeUploadable(uploadable);
                            return Promise.resolve();
                        } else {
                            let error = new Error(
                                "Post already uploaded " +
                                    `(@${searchResult.exactPost.id})`
                            );
                            error.uploadable = uploadable;
                            error.similarPosts = [
                                {
                                    distance: 0,
                                    post: searchResult.exactPost
                                }
                            ];
                            return Promise.reject(error);
                        }
                    }

                    // notify about similar posts
                    if (searchResult.similarPosts.length) {
                        let error = new Error(
                            `Found ${searchResult.similarPosts.length} similar ` +
                                "posts.\nYou can resume or discard this upload."
                        );
                        error.uploadable = uploadable;
                        error.similarPosts = searchResult.similarPosts;
                        return Promise.reject(error);
                    } else if (uploadable.foundOriginal) {
                        return this._copyTagsToOriginalAndSave(
                            uploadable, uploadable.foundOriginal
                        );
                    }
                }

                // no duplicates, proceed with saving
                let post = this._uploadableToPost(uploadable);
                let savePromise = post.save(uploadable.anonymous).then(() => {
                    this._view.removeUploadable(uploadable);
                    return Promise.resolve();
                });
                this._lastCancellablePromise = savePromise;
                return savePromise;
            })
            .then(
                (result) => {
                    progress.done();
                    return Promise.resolve(result);
                },
                (error) => {
                    error.uploadable = uploadable;
                    progress.done();
                    return Promise.reject(error);
                }
            );
    }

    _uploadableToPost(uploadable) {
        let post = new Post();
        post.safety = uploadable.safety;
        post.flags = uploadable.flags;
        for (let tagName of uploadable.tags) {
            const tag = new Tag();
            tag.names = [tagName];
            post.tags.add(tag);
        }
        post.relations = uploadable.relations;
        post.newContent = uploadable.url || uploadable.file;
        // if uploadable.source is ever going to be a valid field (e.g when setting source directly in the upload window)
        // you'll need to change the line below to `post.source = uploadable.source || uploadable.url;`
        if (uploadable.url) {
            post.source = uploadable.url;
        }
        return post;
    }

    _copyTagsToOriginalAndSave(uploadable, original) {
        uploadable.tags.map(tag => original.tags.addByName(tag));
        let savePromise = original.save()
            .then(
                () => {
                    this._view.removeUploadable(uploadable);
                    return Promise.resolve();
                }
            );
        this._lastCancellablePromise = savePromise;
        return savePromise;
    }
}

module.exports = (router) => {
    router.enter(["upload"], (ctx, next) => {
        ctx.controller = new PostUploadController();
    });
};

© 2015 - 2026 Jakob L. Kreuze