<commit_before>/*jshint esversion: 6 */
const Core = require("./core");

const Pinning = module.exports;
const Util = require("../common-util");
const nThen = require("nthen");

//const escapeKeyCharacters = Util.escapeKeyCharacters;
const unescapeKeyCharacters = Util.unescapeKeyCharacters;

var sumChannelSizes = function (sizes) {
    return Object.keys(sizes).map(function (id) { return sizes[id]; })
        .filter(function (x) {
            // only allow positive numbers
            return !(typeof(x) !== 'number' || x <= 0);
        })
        .reduce(function (a, b) { return a + b; }, 0);
};

// FIXME it's possible for this to respond before the server has had a chance
// to fetch the limits. Maybe we should respond with an error...
// or wait until we actually know the limits before responding
var getLimit = Pinning.getLimit = function (Env, safeKey, cb) {
    var unsafeKey = unescapeKeyCharacters(safeKey);
    var limit = Env.limits[unsafeKey];
    var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'?
        Env.defaultStorageLimit: Core.DEFAULT_LIMIT;

    var toSend = limit && typeof(limit.limit) === "number"?
        [limit.limit, limit.plan, limit.note] : [defaultLimit, '', ''];

    cb(void 0, toSend);
};

var getMultipleFileSize = function (Env, channels, cb) {
    Env.getMultipleFileSize(channels, cb);
};

var loadUserPins = function (Env, safeKey, cb) {
    var session = Core.getSession(Env.Sessions, safeKey);

    if (session.channels) {
        return cb(session.channels);
    }

    Env.batchUserPins(safeKey, cb, function (done) {
        Env.getPinState(safeKey, function (err, value) {
            if (!err) {
                // only put this into the cache if it completes
                session.channels = value;
            }
            done(value);
        });
    });
};

var truthyKeys = function (O) {
    try {
        return Object.keys(O).filter(function (k) {
            return O[k];
        });
    } catch (err) {
        return [];
    }
};

var getChannelList = Pinning.getChannelList = function (Env, safeKey, _cb) {
    var cb = Util.once(Util.mkAsync(_cb));
    loadUserPins(Env, safeKey, function (pins) {
        cb(truthyKeys(pins));
    });
};

Pinning.getTotalSize = function (Env, safeKey, cb) {
    var unsafeKey = unescapeKeyCharacters(safeKey);
    var limit = Env.limits[unsafeKey];

    // Get a common key if multiple users share the same quota, otherwise take the public key
    var batchKey = (limit && Array.isArray(limit.users)) ? limit.users.join('') : safeKey;

    Env.batchTotalSize(batchKey, cb, function (done) {
        var channels = [];

        var addUnique = function (channel) {
            if (channels.indexOf(channel) !== -1) { return; }
            channels.push(channel);
        };

        nThen(function (waitFor) {
            // Get the channels list for our user account
            getChannelList(Env, safeKey, waitFor(function (_channels) {
                if (!_channels) {
                    waitFor.abort();
                    return done('INVALID_PIN_LIST');
                }
                _channels.forEach(addUnique);
            }));
            // Get the channels list for users sharing our quota
            if (limit && Array.isArray(limit.users) && limit.users.length > 1) {
                limit.users.forEach(function (key) {
                    if (key === unsafeKey) { return; } // Don't count ourselves twice
                    getChannelList(Env, key, waitFor(function (_channels) {
                        if (!_channels) { return; } // Broken user, don't count their quota
                        _channels.forEach(addUnique);
                    }));
                });
            }
        }).nThen(function () {
            Env.getTotalSize(channels, done);
        });
    });
};

/*  Users should be able to clear their own pin log with an authenticated RPC
*/
Pinning.removePins = function (Env, safeKey, cb) {
    Env.pinStore.removeChannel(safeKey, function (err) {
        Env.Log.info('DELETION_PIN_BY_OWNER_RPC', {
            safeKey: safeKey,
            status: err? String(err): 'SUCCESS',
        });

        if (err) { return void cb(err); }
        cb(void 0, 'OK');
    });
};

Pinning.trimPins = function (Env, safeKey, cb) {
    cb("NOT_IMPLEMENTED");
};

var getFreeSpace = Pinning.getFreeSpace = function (Env, safeKey, cb) {
    getLimit(Env, safeKey, function (e, limit) {
        if (e) { return void cb(e); }
        Pinning.getTotalSize(Env, safeKey, function (e, size) {
            if (typeof(size) === 'undefined') { return void cb(e); }

            var rem = limit[0] - size;
            if (typeof(rem) !== 'number') {
                return void cb('invalid_response');
            }
            cb(void 0, rem);
        });
    });
};

var getHash = Pinning.getHash = function (Env, safeKey, cb) {
    getChannelList(Env, safeKey, function (channels) {
        Env.hashChannelList(channels, cb);
    });
};

Pinning.pinChannel = function (Env, safeKey, channels, cb) {
    if (!channels && channels.filter) {
        return void cb('INVALID_PIN_LIST');
    }

    // get channel list ensures your session has a cached channel list
    getChannelList(Env, safeKey, function (pinned) {
        var session = Core.getSession(Env.Sessions, safeKey);

        // only pin channels which are not already pinned
        var toStore = channels.filter(function (channel) {
            return pinned.indexOf(channel) === -1;
        });

        if (toStore.length === 0) {
            return void getHash(Env, safeKey, cb);
        }

        getMultipleFileSize(Env, toStore, function (e, sizes) {
            if (typeof(sizes) === 'undefined') { return void cb(e); }
            var pinSize = sumChannelSizes(sizes);

            getFreeSpace(Env, safeKey, function (e, free) {
                if (typeof(free) === 'undefined') {
                    Env.WARN('getFreeSpace', e);
                    return void cb(e);
                }
                if (pinSize > free) { return void cb('E_OVER_LIMIT'); }

                Env.pinStore.message(safeKey, JSON.stringify(['PIN', toStore, +new Date()]),
                    function (e) {
                    if (e) { return void cb(e); }
                    toStore.forEach(function (channel) {
                        session.channels[channel] = true;
                    });
                    getHash(Env, safeKey, cb);
                });
            });
        });
    });
};

Pinning.unpinChannel = function (Env, safeKey, channels, cb) {
    if (!channels && channels.filter) {
        // expected array
        return void cb('INVALID_PIN_LIST');
    }

    getChannelList(Env, safeKey, function (pinned) {
        var session = Core.getSession(Env.Sessions, safeKey);

        // only unpin channels which are pinned
        var toStore = channels.filter(function (channel) {
            return pinned.indexOf(channel) !== -1;
        });

        if (toStore.length === 0) {
            return void getHash(Env, safeKey, cb);
        }

        Env.pinStore.message(safeKey, JSON.stringify(['UNPIN', toStore, +new Date()]),
            function (e) {
            if (e) { return void cb(e); }
            toStore.forEach(function (channel) {
                delete session.channels[channel];
            });
            getHash(Env, safeKey, cb);
        });
    });
};

Pinning.resetUserPins = function (Env, safeKey, channelList, cb) {
    if (!Array.isArray(channelList)) { return void cb('INVALID_PIN_LIST'); }
    var session = Core.getSession(Env.Sessions, safeKey);

    if (!channelList.length) {
        return void getHash(Env, safeKey, function (e, hash) {
            if (e) { return cb(e); }
            cb(void 0, hash);
        });
    }

    var pins = {};
    getMultipleFileSize(Env, channelList, function (e, sizes) {
        if (typeof(sizes) === 'undefined') { return void cb(e); }
        var pinSize = sumChannelSizes(sizes);

        getLimit(Env, safeKey, function (e, limit) {
            if (e) {
                Env.WARN('[RESET_ERR]', e);
                return void cb(e);
            }

            /*  we want to let people pin, even if they are over their limit,
                but they should only be able to do this once.

                This prevents data loss in the case that someone registers, but
                does not have enough free space to pin their migrated data.

                They will not be able to pin additional pads until they upgrade
                or delete enough files to go back under their limit. */
            if (pinSize > limit[0] && session.hasPinned) { return void(cb('E_OVER_LIMIT')); }
            Env.pinStore.message(safeKey, JSON.stringify(['RESET', channelList, +new Date()]),
                function (e) {
                if (e) { return void cb(e); }
                channelList.forEach(function (channel) {
                    pins[channel] = true;
                });

                var oldChannels;
                if (session.channels && typeof(session.channels) === 'object') {
                    oldChannels = Object.keys(session.channels);
                } else {
                    oldChannels = [];
                }

                // update in-memory cache IFF the reset was allowed.
                session.channels = pins;
                getHash(Env, safeKey, function (e, hash) {
                    cb(e, hash);
                });
            });
        });
    });
};

Pinning.getFileSize = function (Env, channel, cb) {
    Env.getFileSize(channel, cb);
};

/*  accepts a list, and returns a sublist of channel or file ids which seem
    to have been deleted from the server (file size 0)

    we might consider that we should only say a file is gone if fs.stat returns
    ENOENT, but for now it's simplest to just rely on getFileSize...
*/
Pinning.getDeletedPads = function (Env, channels, cb) {
    Env.getDeletedPads(channels, cb);
};

// FIXME this will be removed from the client
Pinning.isChannelPinned = function (Env, channel, cb) {
    return void cb(void 0, true);
};

<commit_msg>take note of a minor error with pin RPCs<commit_after>/*jshint esversion: 6 */
const Core = require("./core");

const Pinning = module.exports;
const Util = require("../common-util");
const nThen = require("nthen");

//const escapeKeyCharacters = Util.escapeKeyCharacters;
const unescapeKeyCharacters = Util.unescapeKeyCharacters;

var sumChannelSizes = function (sizes) {
    return Object.keys(sizes).map(function (id) { return sizes[id]; })
        .filter(function (x) {
            // only allow positive numbers
            return !(typeof(x) !== 'number' || x <= 0);
        })
        .reduce(function (a, b) { return a + b; }, 0);
};

// FIXME it's possible for this to respond before the server has had a chance
// to fetch the limits. Maybe we should respond with an error...
// or wait until we actually know the limits before responding
var getLimit = Pinning.getLimit = function (Env, safeKey, cb) {
    var unsafeKey = unescapeKeyCharacters(safeKey);
    var limit = Env.limits[unsafeKey];
    var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'?
        Env.defaultStorageLimit: Core.DEFAULT_LIMIT;

    var toSend = limit && typeof(limit.limit) === "number"?
        [limit.limit, limit.plan, limit.note] : [defaultLimit, '', ''];

    cb(void 0, toSend);
};

var getMultipleFileSize = function (Env, channels, cb) {
    Env.getMultipleFileSize(channels, cb);
};

var loadUserPins = function (Env, safeKey, cb) {
    var session = Core.getSession(Env.Sessions, safeKey);

    if (session.channels) {
        return cb(session.channels);
    }

    Env.batchUserPins(safeKey, cb, function (done) {
        Env.getPinState(safeKey, function (err, value) {
            if (!err) {
                // only put this into the cache if it completes
                session.channels = value;
            }
            done(value);
        });
    });
};

var truthyKeys = function (O) {
    try {
        return Object.keys(O).filter(function (k) {
            return O[k];
        });
    } catch (err) {
        return [];
    }
};

var getChannelList = Pinning.getChannelList = function (Env, safeKey, _cb) {
    var cb = Util.once(Util.mkAsync(_cb));
    loadUserPins(Env, safeKey, function (pins) {
        cb(truthyKeys(pins));
    });
};

Pinning.getTotalSize = function (Env, safeKey, cb) {
    var unsafeKey = unescapeKeyCharacters(safeKey);
    var limit = Env.limits[unsafeKey];

    // Get a common key if multiple users share the same quota, otherwise take the public key
    var batchKey = (limit && Array.isArray(limit.users)) ? limit.users.join('') : safeKey;

    Env.batchTotalSize(batchKey, cb, function (done) {
        var channels = [];

        var addUnique = function (channel) {
            if (channels.indexOf(channel) !== -1) { return; }
            channels.push(channel);
        };

        nThen(function (waitFor) {
            // Get the channels list for our user account
            getChannelList(Env, safeKey, waitFor(function (_channels) {
                if (!_channels) {
                    waitFor.abort();
                    return done('INVALID_PIN_LIST');
                }
                _channels.forEach(addUnique);
            }));
            // Get the channels list for users sharing our quota
            if (limit && Array.isArray(limit.users) && limit.users.length > 1) {
                limit.users.forEach(function (key) {
                    if (key === unsafeKey) { return; } // Don't count ourselves twice
                    getChannelList(Env, key, waitFor(function (_channels) {
                        if (!_channels) { return; } // Broken user, don't count their quota
                        _channels.forEach(addUnique);
                    }));
                });
            }
        }).nThen(function () {
            Env.getTotalSize(channels, done);
        });
    });
};

/*  Users should be able to clear their own pin log with an authenticated RPC
*/
Pinning.removePins = function (Env, safeKey, cb) {
    // FIXME respect the queue
    Env.pinStore.removeChannel(safeKey, function (err) {
        Env.Log.info('DELETION_PIN_BY_OWNER_RPC', {
            safeKey: safeKey,
            status: err? String(err): 'SUCCESS',
        });

        if (err) { return void cb(err); }
        cb(void 0, 'OK');
    });
};

Pinning.trimPins = function (Env, safeKey, cb) {
    cb("NOT_IMPLEMENTED");
};

var getFreeSpace = Pinning.getFreeSpace = function (Env, safeKey, cb) {
    getLimit(Env, safeKey, function (e, limit) {
        if (e) { return void cb(e); }
        Pinning.getTotalSize(Env, safeKey, function (e, size) {
            if (typeof(size) === 'undefined') { return void cb(e); }

            var rem = limit[0] - size;
            if (typeof(rem) !== 'number') {
                return void cb('invalid_response');
            }
            cb(void 0, rem);
        });
    });
};

var getHash = Pinning.getHash = function (Env, safeKey, cb) {
    getChannelList(Env, safeKey, function (channels) {
        Env.hashChannelList(channels, cb);
    });
};

Pinning.pinChannel = function (Env, safeKey, channels, cb) {
    if (!channels && channels.filter) {
        return void cb('INVALID_PIN_LIST');
    }

    // get channel list ensures your session has a cached channel list
    getChannelList(Env, safeKey, function (pinned) {
        var session = Core.getSession(Env.Sessions, safeKey);

        // only pin channels which are not already pinned
        var toStore = channels.filter(function (channel) {
            return pinned.indexOf(channel) === -1;
        });

        if (toStore.length === 0) {
            return void getHash(Env, safeKey, cb);
        }

        getMultipleFileSize(Env, toStore, function (e, sizes) {
            if (typeof(sizes) === 'undefined') { return void cb(e); }
            var pinSize = sumChannelSizes(sizes);

            getFreeSpace(Env, safeKey, function (e, free) {
                if (typeof(free) === 'undefined') {
                    Env.WARN('getFreeSpace', e);
                    return void cb(e);
                }
                if (pinSize > free) { return void cb('E_OVER_LIMIT'); }

                Env.pinStore.message(safeKey, JSON.stringify(['PIN', toStore, +new Date()]),
                    function (e) {
                    if (e) { return void cb(e); }
                    toStore.forEach(function (channel) {
                        session.channels[channel] = true;
                    });
                    getHash(Env, safeKey, cb);
                });
            });
        });
    });
};

Pinning.unpinChannel = function (Env, safeKey, channels, cb) {
    if (!channels && channels.filter) {
        // expected array
        return void cb('INVALID_PIN_LIST');
    }

    getChannelList(Env, safeKey, function (pinned) {
        var session = Core.getSession(Env.Sessions, safeKey);

        // only unpin channels which are pinned
        var toStore = channels.filter(function (channel) {
            return pinned.indexOf(channel) !== -1;
        });

        if (toStore.length === 0) {
            return void getHash(Env, safeKey, cb);
        }

        Env.pinStore.message(safeKey, JSON.stringify(['UNPIN', toStore, +new Date()]),
            function (e) {
            if (e) { return void cb(e); }
            toStore.forEach(function (channel) {
                delete session.channels[channel];
            });
            getHash(Env, safeKey, cb);
        });
    });
};

Pinning.resetUserPins = function (Env, safeKey, channelList, cb) {
    if (!Array.isArray(channelList)) { return void cb('INVALID_PIN_LIST'); }
    var session = Core.getSession(Env.Sessions, safeKey);

    if (!channelList.length) {
        return void getHash(Env, safeKey, function (e, hash) {
            if (e) { return cb(e); }
            cb(void 0, hash);
        });
    }

    var pins = {};
    getMultipleFileSize(Env, channelList, function (e, sizes) {
        if (typeof(sizes) === 'undefined') { return void cb(e); }
        var pinSize = sumChannelSizes(sizes);

        getLimit(Env, safeKey, function (e, limit) {
            if (e) {
                Env.WARN('[RESET_ERR]', e);
                return void cb(e);
            }

            /*  we want to let people pin, even if they are over their limit,
                but they should only be able to do this once.

                This prevents data loss in the case that someone registers, but
                does not have enough free space to pin their migrated data.

                They will not be able to pin additional pads until they upgrade
                or delete enough files to go back under their limit. */
            if (pinSize > limit[0] && session.hasPinned) { return void(cb('E_OVER_LIMIT')); }
            Env.pinStore.message(safeKey, JSON.stringify(['RESET', channelList, +new Date()]),
                function (e) {
                if (e) { return void cb(e); }
                channelList.forEach(function (channel) {
                    pins[channel] = true;
                });

                var oldChannels;
                if (session.channels && typeof(session.channels) === 'object') {
                    oldChannels = Object.keys(session.channels);
                } else {
                    oldChannels = [];
                }

                // update in-memory cache IFF the reset was allowed.
                session.channels = pins;
                getHash(Env, safeKey, function (e, hash) {
                    cb(e, hash);
                });
            });
        });
    });
};

Pinning.getFileSize = function (Env, channel, cb) {
    Env.getFileSize(channel, cb);
};

/*  accepts a list, and returns a sublist of channel or file ids which seem
    to have been deleted from the server (file size 0)

    we might consider that we should only say a file is gone if fs.stat returns
    ENOENT, but for now it's simplest to just rely on getFileSize...
*/
Pinning.getDeletedPads = function (Env, channels, cb) {
    Env.getDeletedPads(channels, cb);
};

// FIXME this will be removed from the client
Pinning.isChannelPinned = function (Env, channel, cb) {
    return void cb(void 0, true);
};

<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import _ from 'underscore';
import { console, ajax, DateUtility, object, Schemas } from './../../util';
import { store } from '../../../store';
import * as globals from './../../globals';
import { Collapse, Button } from 'react-bootstrap';
import { PartialList } from './../../item-pages/components';
import { BasicStaticSectionBody } from './BasicStaticSectionBody';

/**
 * A single Announcement block/view.
 */
class Announcement extends React.Component {

    subtitleAuthor(author){
        if (!author) return null;
    }

    subtitle(){
        var section = this.props.section;
        var date = section.date_created;
        var author = section.submitted_by;
        var authorName = (author && author.display_title && { author.display_title }) || null;
        var unreleasedStatus = section.status && section.status !== 'released' ?  - { Schemas.Term.capitalizeSentence(section.status) } : null;
        //var authorLink = authorName && object.itemUtil.atId(author);
        //if (authorLink) authorName = { authorName };
        return (

                { authorName ? Posted by { authorName }: null }
                { date ? {!authorName ? ' Posted ' : ' '}on <DateUtility.LocalizedTime timestamp={date}/> : null }
                { unreleasedStatus }

        );
    }

    render() {
        var section = this.props.section;
        if (!section || !section.content) return null;

        var filetype = section.filetype || 'html';

        return (

                    <span dangerouslySetInnerHTML={{__html: section.title || 'Untitled' }}/>

                { this.subtitle() }

                    <BasicStaticSectionBody content={section.content} filetype={filetype} />

        );
    }

}

class AnnouncementsLoaded extends React.Component {

    static defaultProps = {
        'searchURL' : '/search/?type=StaticSection&section_type=Announcement&sort=-date_created'
    };

    constructor(props){
        super(props);
        this.fetchAnnouncements = this.fetchAnnouncements.bind(this);
        this.handleAnnouncementsSearchResponse = this.handleAnnouncementsSearchResponse.bind(this);
        this.state = {
            'announcements' : props.announcements || null,
            'loading' : false,
            'mounted' : false,
            'loadedAll' : false // TODO when have more announcements in system : create 'see more' function at this level and pass it down to Announcements to load in more.
        };
    }

    componentDidMount(){
        if (!this.state.announcements) this.fetchAnnouncements();
        else this.setState({ 'mounted' : true });
    }

    componentDidUpdate(pastProps){
        if (pastProps.session !== this.props.session) this.fetchAnnouncements();
    }

    fetchAnnouncements(){
        this.setState({ 'loading' : true, 'mounted' : true }, ()=>{
            ajax.load(this.props.searchURL, this.handleAnnouncementsSearchResponse, 'GET', this.handleAnnouncementsSearchResponse);
        });
    }

    handleAnnouncementsSearchResponse(resp){
        var r_announcements = (resp && resp['@graph']) || null;
        if (Array.isArray(r_announcements) && r_announcements.length > 0){
            var loadedAll = typeof resp.total === 'number' && r_announcements.length === resp.total ? true : false;
            this.setState({ 'announcements' : r_announcements, 'loading' : false, 'loadedAll' : loadedAll, 'totalCount' : resp.total });
        } else {
            this.setState({ 'announcements' : null, 'loading' : false, 'loadedAll' : true });
        }
    }

    render (){
        if (Array.isArray(this.state.announcements)){
            return <Announcements {...this.props} loaded={false} announcements={this.state.announcements} total={this.state.totalCount} />;
        }
        if (this.state.loading || !this.state.mounted){
            return

;
        }
        return <Announcements loaded={false} announcements={[]} total={0} />;
    }
}

/**
 * Component which shows currents announcements.
 *
 * @prop {string} className - Outer
 element's className
 * @prop {string} id - Outer
 element's id attribute.
 */
export class Announcements extends React.Component {

    static defaultProps = {
        'announcements' : [],
        'loaded' : false,
        'initiallyVisible' : 3
    }

    constructor(props){
        super(props);
        this.toggleOpen = this.toggleOpen.bind(this);
        this.state = {
            'open' : false
        };
    }

    toggleOpen(){
        this.setState(function({ open }){
            return { 'open' : !open };
        });
    }

    render(){
        var { loaded, announcements, initiallyVisible, className, id, total } = this.props;
        if (loaded) return <AnnouncementsLoaded {...this.props} />;
        if (!total) total = announcements.length;

        var persistent, collapsible = null;

        function createAnnouncement(announce, idx){
            return <Announcement key={announce.title} index={idx} section={announce} icon={collapsible ? true : false} />;
        }

        if (announcements.length > initiallyVisible) {
            persistent = announcements.slice(0,initiallyVisible);
            collapsible = announcements.slice(initiallyVisible);
        } else {
            persistent = announcements;
        }

        if (!announcements || announcements.length === 0){
            return
No announcements
;
        }

        var onSeeMoreButtonClick = this.props.onSeeMoreClick || this.toggleOpen;

        return (

{
                collapsible ? [
                    <PartialList key="list" open={this.state.open} collapsible={collapsible.map(createAnnouncement)} persistent={persistent.map(createAnnouncement)}/>,
                    <Button key="button" bsSize="sm" className="pull-right" onClick={onSeeMoreButtonClick} bsStyle="default">{ !this.state.open ? 'See ' + (total - persistent.length) + ' More' : 'Hide' }</Button>
                ] : persistent.map(createAnnouncement)
            }

        );
    }
}
<commit_msg>Fallback (in case)<commit_after>'use strict';

import React from 'react';
import _ from 'underscore';
import { console, ajax, DateUtility, object, Schemas } from './../../util';
import { store } from '../../../store';
import * as globals from './../../globals';
import { Collapse, Button } from 'react-bootstrap';
import { PartialList } from './../../item-pages/components';
import { BasicStaticSectionBody } from './BasicStaticSectionBody';

/**
 * A single Announcement block/view.
 */
class Announcement extends React.Component {

    subtitleAuthor(author){
        if (!author) return null;
    }

    subtitle(){
        var section = this.props.section;
        var date = section.date_created;
        var author = section.submitted_by;
        var authorName = (author && author.display_title && { author.display_title }) || null;
        var unreleasedStatus = section.status && section.status !== 'released' ?  - { Schemas.Term.capitalizeSentence(section.status) } : null;
        //var authorLink = authorName && object.itemUtil.atId(author);
        //if (authorLink) authorName = { authorName };
        return (

                { authorName ? Posted by { authorName }: null }
                { date ? {!authorName ? ' Posted ' : ' '}on <DateUtility.LocalizedTime timestamp={date}/> : null }
                { unreleasedStatus }

        );
    }

    render() {
        var section = this.props.section;
        if (!section || !section.content) return null;

        var filetype = section.filetype || 'html';

        return (

                    <span dangerouslySetInnerHTML={{__html: section.title || 'Untitled' }}/>

                { this.subtitle() }

                    <BasicStaticSectionBody content={section.content} filetype={filetype} />

        );
    }

}

class AnnouncementsLoaded extends React.Component {

    static defaultProps = {
        'searchURL' : '/search/?type=StaticSection&section_type=Announcement&sort=-date_created'
    };

    constructor(props){
        super(props);
        this.fetchAnnouncements = this.fetchAnnouncements.bind(this);
        this.handleAnnouncementsSearchResponse = this.handleAnnouncementsSearchResponse.bind(this);
        this.state = {
            'announcements' : props.announcements || null,
            'loading' : false,
            'mounted' : false,
            'loadedAll' : false // TODO when have more announcements in system : create 'see more' function at this level and pass it down to Announcements to load in more.
        };
    }

    componentDidMount(){
        if (!this.state.announcements) this.fetchAnnouncements();
        else this.setState({ 'mounted' : true });
    }

    componentDidUpdate(pastProps){
        if (pastProps.session !== this.props.session) this.fetchAnnouncements();
    }

    fetchAnnouncements(){
        this.setState({ 'loading' : true, 'mounted' : true }, ()=>{
            ajax.load(this.props.searchURL, this.handleAnnouncementsSearchResponse, 'GET', this.handleAnnouncementsSearchResponse);
        });
    }

    handleAnnouncementsSearchResponse(resp){
        var r_announcements = (resp && resp['@graph']) || null;
        if (Array.isArray(r_announcements) && r_announcements.length > 0){
            var loadedAll = typeof resp.total === 'number' && r_announcements.length === resp.total ? true : false;
            this.setState({ 'announcements' : r_announcements, 'loading' : false, 'loadedAll' : loadedAll, 'totalCount' : resp.total });
        } else {
            this.setState({ 'announcements' : null, 'loading' : false, 'loadedAll' : true });
        }
    }

    render (){
        if (Array.isArray(this.state.announcements)){
            return <Announcements {...this.props} loaded={false} announcements={this.state.announcements} total={this.state.totalCount} />;
        }
        if (this.state.loading || !this.state.mounted){
            return

;
        }
        return <Announcements loaded={false} announcements={[]} total={0} />;
    }
}

/**
 * Component which shows currents announcements.
 *
 * @prop {string} className - Outer
 element's className
 * @prop {string} id - Outer
 element's id attribute.
 */
export class Announcements extends React.Component {

    static defaultProps = {
        'announcements' : [],
        'loaded' : false,
        'initiallyVisible' : 3
    }

    constructor(props){
        super(props);
        this.toggleOpen = this.toggleOpen.bind(this);
        this.state = {
            'open' : false
        };
    }

    toggleOpen(){
        this.setState(function({ open }){
            return { 'open' : !open };
        });
    }

    render(){
        var { loaded, announcements, initiallyVisible, className, id, total } = this.props;
        if (loaded) return <AnnouncementsLoaded {...this.props} />;

        var announcementsLength = (Array.isArray(announcements) && announcements.length) || 0;
        if (!total) total = announcementsLength;

        var persistent, collapsible = null;

        function createAnnouncement(announce, idx){
            return <Announcement key={announce.title} index={idx} section={announce} icon={collapsible ? true : false} />;
        }

        if (announcementsLength === 0){
            return
No announcements
;
        }

        if (announcementsLength > initiallyVisible) {
            persistent = announcements.slice(0,initiallyVisible);
            collapsible = announcements.slice(initiallyVisible);
        } else {
            persistent = announcements;
        }

        var onSeeMoreButtonClick = this.props.onSeeMoreClick || this.toggleOpen;

        return (

{
                collapsible ? [
                    <PartialList key="list" open={this.state.open} collapsible={collapsible.map(createAnnouncement)} persistent={persistent.map(createAnnouncement)}/>,
                    <Button key="button" bsSize="sm" className="pull-right" onClick={onSeeMoreButtonClick} bsStyle="default">{ !this.state.open ? 'See ' + (total - persistent.length) + ' More' : 'Hide' }</Button>
                ] : persistent.map(createAnnouncement)
            }

        );
    }
}
<|endoftext|>


<commit_before>import 'iframe-resizer/js/iframeResizer.contentWindow.js';

import { override, extend } from 'flarum/extend';
import app from 'flarum/app';
import ForumApplication from 'flarum/ForumApplication';
import Composer from 'flarum/components/Composer';
import PostStream from 'flarum/components/PostStream';
import ModalManager from 'flarum/components/ModalManager';
import AlertManager from 'flarum/components/AlertManager';
import PostMeta from 'flarum/components/PostMeta';
import mapRoutes from 'flarum/utils/mapRoutes';
import Pane from 'flarum/utils/Pane';
import Drawer from 'flarum/utils/Drawer';
import ScrollListener from 'flarum/utils/ScrollListener';

import DiscussionPage from 'flarum/components/DiscussionPage';

extend(ForumApplication.prototype, 'mount', function() {
  if (m.route.param('hideFirstPost')) {
    extend(PostStream.prototype, 'view', vdom => {
      if (vdom.children[0].attrs['data-number'] === 1) {
        vdom.children.splice(0, 1);
      }
    });
  }
});

m.route.mode = 'pathname';

override(m, 'route', function(original, root, arg1, arg2, vdom) {
  if (arguments.length === 1) {

  } else if (arguments.length === 4 && typeof arg1 === 'string') {

  } else if (root.addEventListener || root.attachEvent) {
    root.href = vdom.attrs.href.replace('/embed', '/d');
    root.target = '_blank';

    // TODO: If href leads to a post within this discussion that we have
    // already loaded, then scroll to it?
    return;
  }

  return original.apply(this, Array.prototype.slice.call(arguments, 1));
});

// Trim the /embed prefix off of post permalinks
override(PostMeta.prototype, 'getPermalink', (original, post) => {
  return original(post).replace('/embed', '/d');
});

app.pageInfo = m.prop({});

const reposition = function() {
  const info = app.pageInfo();
  this.$().css('top', Math.max(0, info.scrollTop - info.offsetTop));
};

extend(ModalManager.prototype, 'show', reposition);
extend(Composer.prototype, 'show', reposition);

window.iFrameResizer = {
  readyCallback: function() {
    window.parentIFrame.getPageInfo(app.pageInfo);
  }
};

extend(PostStream.prototype, 'goToNumber', function(promise, number) {
  if (number === 'reply' && 'parentIFrame' in window && app.composer.isFullScreen()) {
    const itemTop = this.$('.PostStream-item:last').offset().top;
    window.parentIFrame.scrollToOffset(0, itemTop);
  }
});

extend(DiscussionPage.prototype, 'sidebarItems', function(items) {
  items.remove('scrubber');

  const count = this.discussion.replyCount();

  items.add('replies',

      {count} comment{count == 1 ? '' : 's'}

, 100);

  const props = items.get('controls').props;
  props.className = props.className.replace('App-primaryControl', '');
});

delete app.routes['index.filter'];
app.routes['discussion'] = {path: '/embed/:id', component: DiscussionPage.component()};
app.routes['discussion.near'] = {path: '/embed/:id/:near', component: DiscussionPage.component()};

<commit_msg>Remove obsolete imports<commit_after>import 'iframe-resizer/js/iframeResizer.contentWindow.js';

import { override, extend } from 'flarum/extend';
import app from 'flarum/app';
import ForumApplication from 'flarum/ForumApplication';
import Composer from 'flarum/components/Composer';
import PostStream from 'flarum/components/PostStream';
import ModalManager from 'flarum/components/ModalManager';
import PostMeta from 'flarum/components/PostMeta';

import DiscussionPage from 'flarum/components/DiscussionPage';

extend(ForumApplication.prototype, 'mount', function() {
  if (m.route.param('hideFirstPost')) {
    extend(PostStream.prototype, 'view', vdom => {
      if (vdom.children[0].attrs['data-number'] === 1) {
        vdom.children.splice(0, 1);
      }
    });
  }
});

m.route.mode = 'pathname';

override(m, 'route', function(original, root, arg1, arg2, vdom) {
  if (arguments.length === 1) {

  } else if (arguments.length === 4 && typeof arg1 === 'string') {

  } else if (root.addEventListener || root.attachEvent) {
    root.href = vdom.attrs.href.replace('/embed', '/d');
    root.target = '_blank';

    // TODO: If href leads to a post within this discussion that we have
    // already loaded, then scroll to it?
    return;
  }

  return original.apply(this, Array.prototype.slice.call(arguments, 1));
});

// Trim the /embed prefix off of post permalinks
override(PostMeta.prototype, 'getPermalink', (original, post) => {
  return original(post).replace('/embed', '/d');
});

app.pageInfo = m.prop({});

const reposition = function() {
  const info = app.pageInfo();
  this.$().css('top', Math.max(0, info.scrollTop - info.offsetTop));
};

extend(ModalManager.prototype, 'show', reposition);
extend(Composer.prototype, 'show', reposition);

window.iFrameResizer = {
  readyCallback: function() {
    window.parentIFrame.getPageInfo(app.pageInfo);
  }
};

extend(PostStream.prototype, 'goToNumber', function(promise, number) {
  if (number === 'reply' && 'parentIFrame' in window && app.composer.isFullScreen()) {
    const itemTop = this.$('.PostStream-item:last').offset().top;
    window.parentIFrame.scrollToOffset(0, itemTop);
  }
});

extend(DiscussionPage.prototype, 'sidebarItems', function(items) {
  items.remove('scrubber');

  const count = this.discussion.replyCount();

  items.add('replies',

      {count} comment{count == 1 ? '' : 's'}

, 100);

  const props = items.get('controls').props;
  props.className = props.className.replace('App-primaryControl', '');
});

delete app.routes['index.filter'];
app.routes['discussion'] = {path: '/embed/:id', component: DiscussionPage.component()};
app.routes['discussion.near'] = {path: '/embed/:id/:near', component: DiscussionPage.component()};

<|endoftext|>


<commit_before>// Encryption module. Loaded in window.onload()
// When page is loaded : Encryption.checkAPI();
var Encryption = (function() {
	// Private
	var chunkSize = 1024 * 1024; // Size of one chunk in B
	var cek = sessionStorage.getItem("cek");
	if (cek == null) { //check if the cek is there
		sessionStorage.clear();
		window.location.href = root+"Logout"; //doesn't exist ? Then logout the user
	}
	var target = 'User';
	var est = 33.5; // Estimation of the difference between the file and encrypted file in %
	var debug = false;

	var errorHandler = function(e) {
		console.log("Error");
		console.log(e);
	};

	var time;

	// Constructor
	function Encryption(f, f_id, i, callback) {
		var me = this;
		document.querySelector("#span_upload"+i).innerHTML = f.name+' : 0%';

		this.callback = callback;
		this.folder_id = f_id;
		this.file = f;	// file.name, file.size
		this.est_size = Math.round(f.size*(1+est/100)); // Estimation of encrypted file size
		this.i = i; // Id of file, used only for displaying progress, different from database !
		this.j = 0; // Number of chunks read
		this.k = 0; // Number of chunks written
		this.l = 0; // Number of B written
		this.m = 0; // Start to write at chunk x
		this.halt = false;

		//crypto parameters
		this.salt = sjcl.random.randomWords(2);

		//key derivation
		this.key = sjcl.misc.pbkdf2(cek, this.salt, 7000, 256);
		this.enc = new sjcl.cipher.aes(this.key);

		var replaceYesAction = function() {
			var file_id = document.querySelector('span.file[data-title="'+f.name+'"]').id;
			if(file_id) {
				Rm.rm(file_id, function(){Upload.read(me.i)}, false);
			} else { alert('Error'); }
		}

		var completeYesAction = function(chkNb) {
			var file_id = document.querySelector('span.file[data-title="'+f.name+'"]').id;
			if(file_id) {
				Upload.read(me.i, chkNb);
			} else { alert('Error'); }
		}

		var noAction = function() {
			// Remove file from uploading files
			me.abort();
		}

		// Check status before uploading
		var xhr = new XMLHttpRequest();
		xhr.open("POST", target+'/getFileStatus', true);
		xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

		xhr.onreadystatechange = function() {
			// TODO : improve alert (only one) and confirm (yes, no, yes for all, no for all)
			if(xhr.status == 200 && xhr.readyState == 4) {
				var filestatus = xhr.responseText.split('@');
				console.log(filestatus);
				if(filestatus[0] == '0') { // File doesn't exist, it's ok
					me.read();
				}
				else if(filestatus[0] == '1' && filestatus.length === 2 && Upload.yesCompleteAll === true) {
					if(isNumeric(filestatus[1]))
						completeYesAction(filestatus[1]);
				}
				else if((filestatus[0] == '1' || filestatus[0] == '2') && Upload.yesReplaceAll === true) {
					replaceYesAction();
				}
				else if((filestatus[0] == '1' || filestatus[0] == '2') && Upload.noAll === true) {
					noAction();
				}
				else if(filestatus[0] == '1' && filestatus.length === 2) { // File exists and not completed
					if(isNumeric(filestatus[1])) {
						var c = false;
						var m = new MessageBox(txt.User.replaceCompleteFile.replace('[filename]', f.name))
							.addToggle(txt.User.complete, txt.User.replace, function() {
								c = true;
							})
							.addButton('Yes', function() {
								if(c)
									replaceYesAction();
								else
									completeYesAction(filestatus[1]);
							})
						    .addButton('Yes for all', function() {
								if(c) {
									Upload.yesReplaceAll = true;
									replaceYesAction();
								}
								else {
									Upload.yesCompleteAll = true;
									completeYesAction(filestatus[1]);
								}
						    })
						    .addButton('No', noAction)
						    .addButton('No for all', function() {
								Upload.noAll = true;
								noAction();
						    })
						    .show();
					}
				}
				else if(filestatus[0] == '2') { // File exists
					var m = new MessageBox(txt.User.replaceFile.replace('[filename]', f.name))
						.addButton('Yes', replaceYesAction)
						.addButton('Yes for all', function() {
							Upload.yesReplaceAll = true;
							replaceYesAction();
						})
						.addButton('No', noAction)
						.addButton('No for all', function() {
							Upload.noAll = true;
							noAction();
						})
						.show();
				}
				else if(filestatus[0] == 'quota') {
					alert(txt.User.quotaExceeded);
				}
				else {
					alert('Error');
				}
			}
		}
		xhr.send("filename="+f.name+"&filesize="+f.size+"&folder_id="+f_id);
	}

	// Public
	Encryption.prototype.checkAPI = function() {
		if(window.File && window.FileReader && window.FileList && window.Blob) {
			reader = new FileReader();
			return true;
		}
		else {
			alert(txt.User.fileAPI);
			return false;
		}
	};

	Encryption.prototype.abort = function() {
		var me = this;

		console.log('abort');
		me.halt = true;
		var node = document.querySelector("#div_upload"+(me.i));
		if(node) {
			while(node.firstChild) // remove all children
				node.removeChild(node.firstChild);
		}

		if(typeof me.callback == 'function') {
			me.callback();
		}
	};

	Encryption.prototype.toBitArrayCodec = function(bytes) {
		/** Convert from an array of bytes to a bitArray. */
		var out = [], i, tmp=0;
		for (i=0; i<bytes.length; i++) {
			tmp = tmp << 8 | bytes[i];
			if ((i&3) === 3) {
				out.push(tmp);
				tmp = 0;
			}
		}
		if (i&3)
			out.push(sjcl.bitArray.partial(8*(i&3), tmp));
		return out;
	};

	Encryption.prototype.read = function(chkNb = 0) {
		console.log(chkNb);
		this.m = chkNb;
		var me = this;

		if(typeof me.callback == 'function') {
			me.callback();
		}

		time = new Time();
		if(debug) {
			console.log('File size : '+this.file.size);
			console.log('File name : '+this.file.name);
		}

		this.parseFile(this.file, {
			binary: true,
			chunk_size: chunkSize,
			success: function(i) {
				if(me.halt)
					return false;
				// Waiting end of the uploading process
				var timer = setInterval(function() {
					if(debug)
						console.log("Waiting...");
					if(me.k >= me.j) {
						// Done, write "EOF" at the end of file
						clearInterval(timer);

						var xhr = new XMLHttpRequest();
						xhr.open("POST", target+'/writeChunk', true);
						xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

						xhr.onreadystatechange = function() {
							if(xhr.status == 200 && xhr.readyState == 4) {
								time.stop();//
								var node = document.querySelector("#div_upload"+(me.i));
								while(node.firstChild) // remove all children
									node.removeChild(node.firstChild);
								if(debug) {
									console.log("Split + encryption : "+time.elapsed()+" ms");//
									console.log("Splitted in "+me.j+" chunks !");
								}
								if(typeof me.callback !== 'function') {
									Folders.open(me.folder_id);
								}
							}
						}
						xhr.send("filename="+me.file.name+"&data=EOF&folder_id="+me.folder_id);
					}
				}, 1000);
			},
			chunk_read_callback: function(chk) {
				// Reading a chunk
				if(me.halt)
					return false;
				me.j++;
				if(me.m < me.j) {
					chk = new Uint8Array(chk);
					chk = me.toBitArrayCodec(chk);
					var chk_length = me.encryptChk(chk);
					if(debug)
						console.log(me.file.name+' - Part '+me.j+' size : '+chk_length);
				}
				else {
					me.k++;
					console.log('Did not write part '+me.j);
				}
			},
			error_callback: errorHandler
		});
	};

	Encryption.prototype.parseFile = function(file, options) {
		var opts       = typeof options === 'undefined' ? {} : options;
		var fileSize   = file.size;
		var chunkSize  = typeof opts['chunk_size'] === 'undefined' ?  64 * 1024 : parseInt(opts['chunk_size']); // bytes
		var binary     = typeof opts['binary'] === 'undefined' ? false : opts['binary'] == true;
		var offset     = 0;
		var self       = this; // we need a reference to the current object
		var readBlock  = null;
		var chunkReadCallback = typeof opts['chunk_read_callback'] === 'function' ? opts['chunk_read_callback'] : function() {};
		var chunkErrorCallback = typeof opts['error_callback'] === 'function' ? opts['error_callback'] : function() {};
		var success = typeof opts['success'] === 'function' ? opts['success'] : function() {};

		var onLoadHandler = function(evt) {
			var current_chk_length = evt.target.result.length;
			if(current_chk_length === undefined)
				current_chk_length = evt.loaded;

			if (evt.target.error == null && current_chk_length !== undefined) {
				offset += current_chk_length;
				chunkReadCallback(evt.target.result);
			} else {
				chunkErrorCallback(evt.target.error);
				return;
			}
			if (offset >= fileSize) {
				success(file);
				return;
			}

			readBlock(offset, chunkSize, file);
		}

		readBlock = function(_offset, length, _file) {
			var r = new FileReader();
			var blob = _file.slice(_offset, length + _offset);
			r.onload = onLoadHandler;
			if (binary) {
				r.readAsArrayBuffer(blob);
			} else {
				r.readAsText(blob);
			}
		}
		readBlock(offset, chunkSize, file);
	};

	Encryption.prototype.encryptChk = function(chk) {
		if(this.halt)
			return false;
		var me = this;

		var pack = function(c, s, a, i){ //ciphered_chk, salt, authentification data, initialization vector
			var c = sjcl.codec.base64.fromBits(c);
			var s = sjcl.codec.base64.fromBits(s);
			var a = sjcl.codec.base64.fromBits(a);
			var i = sjcl.codec.base64.fromBits(i);
			var t = c+":"+s+":"+a+":"+i;
			return t;
		}

		//crypto parameter
		var initVector = sjcl.random.randomWords(4);
		var aDATA = sjcl.random.randomWords(4);

		//chunk encryption
		var s = sjcl.mode.gcm.encrypt(this.enc, chk, initVector, aDATA, 128);
		s = pack(s, this.salt, aDATA, initVector);

		var xhr = new XMLHttpRequest();
		xhr.open("POST", target+'/writeChunk', true);
		xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

		xhr.onreadystatechange = function() {
			if(xhr.status == 200 && xhr.readyState == 4) {
				console.log(xhr.responseText);
				me.l += s.length;
				var pct = me.l/me.est_size*100;
				if(pct > 100)
					pct = 100;
				document.querySelector("#span_upload"+(me.i)).innerHTML = me.file.name+' : '+pct.toFixed(2)+'%';

				if(xhr.responseText == 'error') {
					// Quota exceeded or unable to write
					console.log('unable to write');
					me.halt = true;
					return false;
				}
				me.k++;
			}
		}
		xhr.send("filename="+this.file.name+"&data="+encodeURIComponent(s)+"&folder_id="+this.folder_id);
		return s.length;
	};

	return Encryption;
});
<commit_msg>Removed "yes all" or "no all" in messageBox if we upload only one file or if this is the last file<commit_after>// Encryption module. Loaded in window.onload()
// When page is loaded : Encryption.checkAPI();
var Encryption = (function() {
	// Private
	var chunkSize = 1024 * 1024; // Size of one chunk in B
	var cek = sessionStorage.getItem("cek");
	if (cek == null) { //check if the cek is there
		sessionStorage.clear();
		window.location.href = root+"Logout"; //doesn't exist ? Then logout the user
	}
	var target = 'User';
	var est = 33.5; // Estimation of the difference between the file and encrypted file in %
	var debug = false;

	var errorHandler = function(e) {
		console.log("Error");
		console.log(e);
	};

	var time;

	// Constructor
	function Encryption(f, f_id, i, callback) {
		var me = this;
		document.querySelector("#span_upload"+i).innerHTML = f.name+' : 0%';

		this.callback = callback;
		this.folder_id = f_id;
		this.file = f;	// file.name, file.size
		this.est_size = Math.round(f.size*(1+est/100)); // Estimation of encrypted file size
		this.i = i; // Id of file, used only for displaying progress, different from database !
		this.j = 0; // Number of chunks read
		this.k = 0; // Number of chunks written
		this.l = 0; // Number of B written
		this.m = 0; // Start to write at chunk x
		this.halt = false;

		//crypto parameters
		this.salt = sjcl.random.randomWords(2);

		//key derivation
		this.key = sjcl.misc.pbkdf2(cek, this.salt, 7000, 256);
		this.enc = new sjcl.cipher.aes(this.key);

		var replaceYesAction = function() {
			var file_id = document.querySelector('span.file[data-title="'+f.name+'"]').id;
			if(file_id) {
				Rm.rm(file_id, function(){Upload.read(me.i)}, false);
			} else { alert('Error'); }
		}

		var completeYesAction = function(chkNb) {
			var file_id = document.querySelector('span.file[data-title="'+f.name+'"]').id;
			if(file_id) {
				Upload.read(me.i, chkNb);
			} else { alert('Error'); }
		}

		var noAction = function() {
			// Remove file from uploading files
			me.abort();
		}

		// Check status before uploading
		var xhr = new XMLHttpRequest();
		xhr.open("POST", target+'/getFileStatus', true);
		xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

		xhr.onreadystatechange = function() {
			// TODO : improve alert (only one) and confirm (yes, no, yes for all, no for all)
			if(xhr.status == 200 && xhr.readyState == 4) {
				var filestatus = xhr.responseText.split('@');
				console.log(filestatus);
				if(filestatus[0] == '0') { // File doesn't exist, it's ok
					me.read();
				}
				else if(filestatus[0] == '1' && filestatus.length === 2 && Upload.yesCompleteAll === true) {
					if(isNumeric(filestatus[1]))
						completeYesAction(filestatus[1]);
				}
				else if((filestatus[0] == '1' || filestatus[0] == '2') && Upload.yesReplaceAll === true) {
					replaceYesAction();
				}
				else if((filestatus[0] == '1' || filestatus[0] == '2') && Upload.noAll === true) {
					noAction();
				}
				else if(filestatus[0] == '1' && filestatus.length === 2) { // File exists and not completed
					if(isNumeric(filestatus[1])) {
						var c = false;
						if(typeof me.callback != 'function') { // Only one file or this is the last file
							var m = new MessageBox(txt.User.replaceFile.replace('[filename]', f.name))
								.addToggle(txt.User.complete, txt.User.replace, function() {
									c = true;
								})
								.addButton('Yes', function() {
									if(c)
										replaceYesAction();
									else
										completeYesAction(filestatus[1]);
								})
								.addButton('No', noAction)
								.show();
						}
						else {
							var m = new MessageBox(txt.User.replaceCompleteFile.replace('[filename]', f.name))
								.addToggle(txt.User.complete, txt.User.replace, function() {
									c = true;
								})
								.addButton('Yes', function() {
									if(c)
										replaceYesAction();
									else
										completeYesAction(filestatus[1]);
								})
							    .addButton('Yes for all', function() {
									if(c) {
										Upload.yesReplaceAll = true;
										replaceYesAction();
									}
									else {
										Upload.yesCompleteAll = true;
										completeYesAction(filestatus[1]);
									}
							    })
							    .addButton('No', noAction)
							    .addButton('No for all', function() {
									Upload.noAll = true;
									noAction();
							    })
							    .show();
						}
					}
				}
				else if(filestatus[0] == '2') { // File exists
					if(typeof me.callback != 'function') { // Only one file or this is the last file
						var m = new MessageBox(txt.User.replaceFile.replace('[filename]', f.name)).addButton('Yes', replaceYesAction).addButton('No', noAction).show();
					}
					else {
						var m = new MessageBox(txt.User.replaceFile.replace('[filename]', f.name))
							.addButton('Yes', replaceYesAction)
							.addButton('Yes for all', function() {
								Upload.yesReplaceAll = true;
								replaceYesAction();
							})
							.addButton('No', noAction)
							.addButton('No for all', function() {
								Upload.noAll = true;
								noAction();
							})
							.show();
					}
				}
				else if(filestatus[0] == 'quota') {
					alert(txt.User.quotaExceeded);
				}
				else {
					alert('Error');
				}
			}
		}
		xhr.send("filename="+f.name+"&filesize="+f.size+"&folder_id="+f_id);
	}

	// Public
	Encryption.prototype.checkAPI = function() {
		if(window.File && window.FileReader && window.FileList && window.Blob) {
			reader = new FileReader();
			return true;
		}
		else {
			alert(txt.User.fileAPI);
			return false;
		}
	};

	Encryption.prototype.abort = function() {
		var me = this;

		console.log('abort');
		me.halt = true;
		var node = document.querySelector("#div_upload"+(me.i));
		if(node) {
			while(node.firstChild) // remove all children
				node.removeChild(node.firstChild);
		}

		if(typeof me.callback == 'function') {
			me.callback();
		}
	};

	Encryption.prototype.toBitArrayCodec = function(bytes) {
		/** Convert from an array of bytes to a bitArray. */
		var out = [], i, tmp=0;
		for (i=0; i<bytes.length; i++) {
			tmp = tmp << 8 | bytes[i];
			if ((i&3) === 3) {
				out.push(tmp);
				tmp = 0;
			}
		}
		if (i&3)
			out.push(sjcl.bitArray.partial(8*(i&3), tmp));
		return out;
	};

	Encryption.prototype.read = function(chkNb = 0) {
		console.log(chkNb);
		this.m = chkNb;
		var me = this;

		if(typeof me.callback == 'function') {
			me.callback();
		}

		time = new Time();
		if(debug) {
			console.log('File size : '+this.file.size);
			console.log('File name : '+this.file.name);
		}

		this.parseFile(this.file, {
			binary: true,
			chunk_size: chunkSize,
			success: function(i) {
				if(me.halt)
					return false;
				// Waiting end of the uploading process
				var timer = setInterval(function() {
					if(debug)
						console.log("Waiting...");
					if(me.k >= me.j) {
						// Done, write "EOF" at the end of file
						clearInterval(timer);

						var xhr = new XMLHttpRequest();
						xhr.open("POST", target+'/writeChunk', true);
						xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

						xhr.onreadystatechange = function() {
							if(xhr.status == 200 && xhr.readyState == 4) {
								time.stop();//
								var node = document.querySelector("#div_upload"+(me.i));
								while(node.firstChild) // remove all children
									node.removeChild(node.firstChild);
								if(debug) {
									console.log("Split + encryption : "+time.elapsed()+" ms");//
									console.log("Splitted in "+me.j+" chunks !");
								}
								if(typeof me.callback != 'function') {
									Folders.open(me.folder_id);
								}
							}
						}
						xhr.send("filename="+me.file.name+"&data=EOF&folder_id="+me.folder_id);
					}
				}, 1000);
			},
			chunk_read_callback: function(chk) {
				// Reading a chunk
				if(me.halt)
					return false;
				me.j++;
				if(me.m < me.j) {
					chk = new Uint8Array(chk);
					chk = me.toBitArrayCodec(chk);
					var chk_length = me.encryptChk(chk);
					if(debug)
						console.log(me.file.name+' - Part '+me.j+' size : '+chk_length);
				}
				else {
					me.k++;
					console.log('Did not write part '+me.j);
				}
			},
			error_callback: errorHandler
		});
	};

	Encryption.prototype.parseFile = function(file, options) {
		var opts       = typeof options === 'undefined' ? {} : options;
		var fileSize   = file.size;
		var chunkSize  = typeof opts['chunk_size'] === 'undefined' ?  64 * 1024 : parseInt(opts['chunk_size']); // bytes
		var binary     = typeof opts['binary'] === 'undefined' ? false : opts['binary'] == true;
		var offset     = 0;
		var self       = this; // we need a reference to the current object
		var readBlock  = null;
		var chunkReadCallback = typeof opts['chunk_read_callback'] === 'function' ? opts['chunk_read_callback'] : function() {};
		var chunkErrorCallback = typeof opts['error_callback'] === 'function' ? opts['error_callback'] : function() {};
		var success = typeof opts['success'] === 'function' ? opts['success'] : function() {};

		var onLoadHandler = function(evt) {
			var current_chk_length = evt.target.result.length;
			if(current_chk_length === undefined)
				current_chk_length = evt.loaded;

			if (evt.target.error == null && current_chk_length !== undefined) {
				offset += current_chk_length;
				chunkReadCallback(evt.target.result);
			} else {
				chunkErrorCallback(evt.target.error);
				return;
			}
			if (offset >= fileSize) {
				success(file);
				return;
			}

			readBlock(offset, chunkSize, file);
		}

		readBlock = function(_offset, length, _file) {
			var r = new FileReader();
			var blob = _file.slice(_offset, length + _offset);
			r.onload = onLoadHandler;
			if (binary) {
				r.readAsArrayBuffer(blob);
			} else {
				r.readAsText(blob);
			}
		}
		readBlock(offset, chunkSize, file);
	};

	Encryption.prototype.encryptChk = function(chk) {
		if(this.halt)
			return false;
		var me = this;

		var pack = function(c, s, a, i){ //ciphered_chk, salt, authentification data, initialization vector
			var c = sjcl.codec.base64.fromBits(c);
			var s = sjcl.codec.base64.fromBits(s);
			var a = sjcl.codec.base64.fromBits(a);
			var i = sjcl.codec.base64.fromBits(i);
			var t = c+":"+s+":"+a+":"+i;
			return t;
		}

		//crypto parameter
		var initVector = sjcl.random.randomWords(4);
		var aDATA = sjcl.random.randomWords(4);

		//chunk encryption
		var s = sjcl.mode.gcm.encrypt(this.enc, chk, initVector, aDATA, 128);
		s = pack(s, this.salt, aDATA, initVector);

		var xhr = new XMLHttpRequest();
		xhr.open("POST", target+'/writeChunk', true);
		xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

		xhr.onreadystatechange = function() {
			if(xhr.status == 200 && xhr.readyState == 4) {
				console.log(xhr.responseText);
				me.l += s.length;
				var pct = me.l/me.est_size*100;
				if(pct > 100)
					pct = 100;
				document.querySelector("#span_upload"+(me.i)).innerHTML = me.file.name+' : '+pct.toFixed(2)+'%';

				if(xhr.responseText == 'error') {
					// Quota exceeded or unable to write
					console.log('unable to write');
					me.halt = true;
					return false;
				}
				me.k++;
			}
		}
		xhr.send("filename="+this.file.name+"&data="+encodeURIComponent(s)+"&folder_id="+this.folder_id);
		return s.length;
	};

	return Encryption;
});
<|endoftext|>


<commit_before>var map, pointarray, heatmap, coordinateObjects;

function initialize(event, search, interval) {
    $.getJSON(window.location.pathname+ "/info", function(data) {
        var event_title = data['event_title']
        var event_name = data['event']
        var icon = data['icon']
        var latitude = data['latitude']
        var longitude = data['longitude']
        var zoom = parseInt(data['zoom'])
        var suggested_search = data['suggested_search']
        var event_date = data['event_date_display']
        console.log(event_date)
        console.log(suggested_search)

        ///adding suggest search terms and title
        $("#search_term").attr("placeholder","e.g. " + suggested_search)
        $("header h1").append(" " + event_title +"")
        $("#event_date p").text(event_date)

        search = search || "none"
        interval = interval || "all"
        $.getJSON("/weekend/instagram/" + event_name + "/" + search + "/" + interval, function(data) {
            var all_posts = data['all_post_info'];
            coordinateObjects = []
            for (i in all_posts) {
              coordinateObjects.push(new google.maps.LatLng(all_posts[i].latitude, all_posts[i].longitude))
            }
            var mapOptions = {
            zoom: zoom,
            center: new google.maps.LatLng(latitude, longitude),
            mapTypeId: google.maps.MapTypeId.ROADMAP
            };

            map = new google.maps.Map(document.getElementById('map-canvas'),
              mapOptions);

            var pointArray = new google.maps.MVCArray(coordinateObjects);

            var icon_link = icon

            var marker_objects = []
            for (var i in coordinateObjects) {
                var marker = new google.maps.Marker({
                    position: coordinateObjects[i],
                    map: map,
                    icon: icon_link,
                    id: i
                })

                marker_objects.push(marker)
            }

            for (var i in marker_objects) {

                google.maps.event.addListener(marker_objects[i], 'click', function() {
                    var id = this.id
                    // infowindows[id].open(map, this);
                    $("#post").html('
'+
                      '
'+
                      '
'+
                      '
 ' + all_posts[id].user + '
'+
                      '
' +
                      ' ' +
                      '
' + all_posts[id].created_time + '
' +
                      '
Caption: ' + all_posts[id].caption + '
' +
                      '
'+
                      '
')

                });
            }

        })
    })

}

google.maps.event.addDomListener(window, 'load', initialize);

$(document).ready(function(event) {
    $("form").on("submit", function(event){
        event.preventDefault()
        var search = $('[name=search_field]').val();
        var interval = $('[name=time_interval]').val();
        initialize(event, search, interval);
    })
})<commit_msg>Last small change<commit_after>var map, pointarray, heatmap, coordinateObjects;

function initialize(event, search, interval) {
    $.getJSON(window.location.pathname+ "/info", function(data) {
        var event_title = data['event_title']
        var event_name = data['event']
        var icon = data['icon']
        var latitude = data['latitude']
        var longitude = data['longitude']
        var zoom = parseInt(data['zoom'])
        var suggested_search = data['suggested_search']
        var event_date = data['event_date_display']
        console.log(event_date)
        console.log(suggested_search)

        ///adding suggest search terms and title
        $("#search_term").attr("placeholder","e.g. " + suggested_search)
        $("header h1").html("Instavent " + event_title +"")
        $("#event_date p").text(event_date)

        search = search || "none"
        interval = interval || "all"
        $.getJSON("/weekend/instagram/" + event_name + "/" + search + "/" + interval, function(data) {
            var all_posts = data['all_post_info'];
            coordinateObjects = []
            for (i in all_posts) {
              coordinateObjects.push(new google.maps.LatLng(all_posts[i].latitude, all_posts[i].longitude))
            }
            var mapOptions = {
            zoom: zoom,
            center: new google.maps.LatLng(latitude, longitude),
            mapTypeId: google.maps.MapTypeId.ROADMAP
            };

            map = new google.maps.Map(document.getElementById('map-canvas'),
              mapOptions);

            var pointArray = new google.maps.MVCArray(coordinateObjects);

            var icon_link = icon

            var marker_objects = []
            for (var i in coordinateObjects) {
                var marker = new google.maps.Marker({
                    position: coordinateObjects[i],
                    map: map,
                    icon: icon_link,
                    id: i
                })

                marker_objects.push(marker)
            }

            for (var i in marker_objects) {

                google.maps.event.addListener(marker_objects[i], 'click', function() {
                    var id = this.id
                    // infowindows[id].open(map, this);
                    $("#post").html('
'+
                      '
'+
                      '
'+
                      '
 ' + all_posts[id].user + '
'+
                      '
' +
                      ' ' +
                      '
' + all_posts[id].created_time + '
' +
                      '
Caption: ' + all_posts[id].caption + '
' +
                      '
'+
                      '
')

                });
            }

        })
    })

}

google.maps.event.addDomListener(window, 'load', initialize);

$(document).ready(function(event) {
    $("form").on("submit", function(event){
        event.preventDefault()
        var search = $('[name=search_field]').val();
        var interval = $('[name=time_interval]').val();
        initialize(event, search, interval);
    })
})<|endoftext|>


<commit_before>var fs = require('fs'),
    roots = require('../index'),
    path = require('path');

var _version = function(){
  var version = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'))).version;
  roots.print.log(version);
};

module.exports = { execute: _version };
<commit_msg>remove unnecessary file read in version command<commit_after>var fs = require('fs'),
    roots = require('../index'),
    path = require('path');

var _version = function(){
  var version = require(path.join(__dirname, '../../package.json')).version;
  roots.print.log(version);
};

module.exports = { execute: _version };
<|endoftext|>


<commit_before>// Includes file dependencies
define([
	"jquery",
	"backbone",
    "marionette",
    "text!../templates/patronage-list-item.html",
    "backform",
    "bootstrap-datepicker",
    "moment",
    "../helpers/UserWreqr"
], function( $, Backbone, Marionette, patronage_html, Backform, datepicker, moment, UserChannel ) {

    // Extends Backbone.View
    var View = Backbone.View.extend( {
        initialize: function(options) {
            var view = this;
            view.options = options;
            var momentFormat = 'MM/DD/YYYY';
            var datepickerFormat = 'mm/dd/yyyy';
            var backmodel = view.model.clone();
            if (backmodel.has("owner")) {
                backmodel.set("owner", backmodel.get("owner").id);
            } else {
                view.isNew = true;
            }
            if (backmodel.has("paidOn")) {
                backmodel.set("paidOn", moment(backmodel.get("paidOn")).format(momentFormat));
            }
            if (backmodel.has("expiresOn")) {
                backmodel.set("expiresOn", moment(backmodel.get("expiresOn")).format(momentFormat));
            }
            var ownerOptions = UserChannel.reqres.request('all').map(function (u) {
                return {
                    label: "" + u.get("username") + " " + u.get("realname") + " " + u.get("email"),
                    value: u.id
                }
            });
            ownerOptions.unshift({label: "Invalid", value: ""});

            view.form = new Backform.Form({
                el: view.$el,
                model: backmodel,
                fields: [
                    {
                        name: "owner",
                        label: "Owner",
                        control: "select",
                        options: ownerOptions
                    },
                    {
                        name: "paidOn",
                        label: "Paid on",
                        control: "datepicker",
                        type: "text",
                        options: {
                            autoclose: true,
                            format: datepickerFormat
                        }
                    },
                    {
                        name: "expiresOn",
                        label: "Expires on",
                        control: "datepicker",
                        type: "text",
                        options: {
                            autoclose: true,
                            format: datepickerFormat
                        }
                    },
                    {
                        name: "submit",
                        control: "button",
                        label: "Save Changes"
                    }
                ],
                events: {
                    "submit": function (e) {
                        var self = this;
                        e.preventDefault();
                        self.fields.at(3).set({status: "", message: ""});
                        self.model.errorModel.clear();
                        view.model.set({
                            "paidOn": moment(self.model.get("paidOn"), momentFormat).toDate(),
                            "expiresOn": moment(self.model.get("expiresOn"), momentFormat).toDate(),
                            "owner": new Parse.User({id: self.model.get("owner")})
                        })
                        view.model.save().then(function () {
                            self.fields.at(3).set({status: "success", message: "Save completed"});
                            _.defer(function () {
                                $("body").enhanceWithin();
                            });
                        }).fail(function(error) {
                            self.model.errorModel.set("owner", _.escape(error.message));
                        })
                    }
                }
            });
        },
        tagName: 'form',
        render: function() {
            this.form.render();
            this.$el.enhanceWithin();

            return this;
        }
    } );

    // Returns the View class
    return View;

} );<commit_msg>Create Patronages with Administrator ACL<commit_after>// Includes file dependencies
define([
	"jquery",
	"backbone",
    "marionette",
    "text!../templates/patronage-list-item.html",
    "backform",
    "bootstrap-datepicker",
    "moment",
    "../helpers/UserWreqr"
], function( $, Backbone, Marionette, patronage_html, Backform, datepicker, moment, UserChannel ) {

    // Extends Backbone.View
    var View = Backbone.View.extend( {
        initialize: function(options) {
            var view = this;
            view.options = options;
            var momentFormat = 'MM/DD/YYYY';
            var datepickerFormat = 'mm/dd/yyyy';
            var backmodel = view.model.clone();
            if (backmodel.has("owner")) {
                backmodel.set("owner", backmodel.get("owner").id);
            } else {
                view.isNew = true;
            }
            if (backmodel.has("paidOn")) {
                backmodel.set("paidOn", moment(backmodel.get("paidOn")).format(momentFormat));
            }
            if (backmodel.has("expiresOn")) {
                backmodel.set("expiresOn", moment(backmodel.get("expiresOn")).format(momentFormat));
            }
            var ownerOptions = UserChannel.reqres.request('all').map(function (u) {
                return {
                    label: "" + u.get("username") + " " + u.get("realname") + " " + u.get("email"),
                    value: u.id
                }
            });
            ownerOptions.unshift({label: "Invalid", value: ""});

            view.form = new Backform.Form({
                el: view.$el,
                model: backmodel,
                fields: [
                    {
                        name: "owner",
                        label: "Owner",
                        control: "select",
                        options: ownerOptions
                    },
                    {
                        name: "paidOn",
                        label: "Paid on",
                        control: "datepicker",
                        type: "text",
                        options: {
                            autoclose: true,
                            format: datepickerFormat
                        }
                    },
                    {
                        name: "expiresOn",
                        label: "Expires on",
                        control: "datepicker",
                        type: "text",
                        options: {
                            autoclose: true,
                            format: datepickerFormat
                        }
                    },
                    {
                        name: "submit",
                        control: "button",
                        label: "Save Changes"
                    }
                ],
                events: {
                    "submit": function (e) {
                        var self = this;
                        e.preventDefault();
                        self.fields.at(3).set({status: "", message: ""});
                        self.model.errorModel.clear();
                        view.model.set({
                            "paidOn": moment(self.model.get("paidOn"), momentFormat).toDate(),
                            "expiresOn": moment(self.model.get("expiresOn"), momentFormat).toDate(),
                            "owner": new Parse.User({id: self.model.get("owner")})
                        })
                        var acl = new Parse.ACL;
                        acl.setPublicReadAccess(true);
                        acl.setPublicWriteAccess(false);
                        acl.setRoleReadAccess("Administrator", true);
                        acl.setRoleWriteAccess("Administrator", true);
                        view.model.setACL(acl);
                        view.model.save().then(function () {
                            self.fields.at(3).set({status: "success", message: "Save completed"});
                            _.defer(function () {
                                $("body").enhanceWithin();
                            });
                        }).fail(function(error) {
                            self.model.errorModel.set("owner", _.escape(error.message));
                        })
                    }
                }
            });
        },
        tagName: 'form',
        render: function() {
            this.form.render();
            this.$el.enhanceWithin();

            return this;
        }
    } );

    // Returns the View class
    return View;

} );<|endoftext|>


<commit_before>$('#map').empty();

//Define coordinate system using PROJ4 standards
var osgb = new L.Proj.CRS('EPSG:27700',
  '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000' +
  '+ellps=airy +datum=OSGB36 +units=m +no_defs',
  {
    resolutions: [2500, 1000, 500, 200, 100, 50, 25, 10, 5, 2.5, 1],
    bounds: L.bounds([1300000,0],[700000,0])
  }
);

//Leaflet map with OS options
var map = new L.Map('map', {
  crs: osgb,
  continuousWorld: true,
  worldCopyJump: false,
  minZoom: 1,
  maxZoom: 10,

  // controls
  dragging: false,
  touchZoom: false,
  doubleClickZoom: false,
  scrollWheelZoom: false,
  boxZoom: false,
  keyboard: false,
  tap: false,
  zoomControl: false,
  attributionControl: false
});

//New OSOpenSpace layer with API Key
var openspaceLayer = L.tileLayer.osopenspace(apiKey, {debug: true});
map.addLayer(openspaceLayer);

//Define name of CRS in GeoJSON using PROJ4
proj4.defs("urn:ogc:def:crs:EPSG:27700","+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs");

//Extent style
var extentStyle = {
  color: 'red',
  fillOpacity: 0.0,
  opacity: 1
}

//Create the extent layer
var extentGeoJson = L.Proj.geoJson(extentData, {
  style: extentStyle
})

//Center map view on geojson polygon
var bounds = extentGeoJson.getBounds();
var center = bounds.getCenter();
map.setView([center.lat, center.lng], 10)
map.fitBounds(bounds, {maxZoom: 10, animate: false});

//Add a scale control to the map
L.control.scale().addTo(map);

//Add layers to the map
extentGeoJson.addTo(map);
<commit_msg>Revert zoom level<commit_after>$('#map').empty();

//Define coordinate system using PROJ4 standards
var osgb = new L.Proj.CRS('EPSG:27700',
  '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000' +
  '+ellps=airy +datum=OSGB36 +units=m +no_defs',
  {
    resolutions: [2500, 1000, 500, 200, 100, 50, 25, 10, 5, 2.5, 1],
    bounds: L.bounds([1300000,0],[700000,0])
  }
);

//Leaflet map with OS options
var map = new L.Map('map', {
  crs: osgb,
  continuousWorld: true,
  worldCopyJump: false,
  minZoom: 1,
  maxZoom: 10,

  // controls
  dragging: false,
  touchZoom: false,
  doubleClickZoom: false,
  scrollWheelZoom: false,
  boxZoom: false,
  keyboard: false,
  tap: false,
  zoomControl: false,
  attributionControl: false
});

//New OSOpenSpace layer with API Key
var openspaceLayer = L.tileLayer.osopenspace(apiKey, {debug: true});
map.addLayer(openspaceLayer);

//Define name of CRS in GeoJSON using PROJ4
proj4.defs("urn:ogc:def:crs:EPSG:27700","+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs");

//Extent style
var extentStyle = {
  color: 'red',
  fillOpacity: 0.0,
  opacity: 1
}

//Create the extent layer
var extentGeoJson = L.Proj.geoJson(extentData, {
  style: extentStyle
})

//Center map view on geojson polygon
var bounds = extentGeoJson.getBounds();
var center = bounds.getCenter();
map.setView([center.lat, center.lng], 9)
map.fitBounds(bounds, {maxZoom: 9, animate: false});

//Add a scale control to the map
L.control.scale().addTo(map);

//Add layers to the map
extentGeoJson.addTo(map);
<|endoftext|>


<commit_before>var imports         = require('soop').imports();
var extend          = imports.extend || require('extend');
var log             = imports.log || require('../util/log');
var bitcoreDefaults = imports.config || require('../config');
var Connection      = imports.Connection || require ('./Connection');

var Peer            = imports.Peer || require('./Peer');

GetAdjustedTime = imports.GetAdjustedTime || function () {
  // TODO: Implement actual adjustment
  return Math.floor(new Date().getTime() / 1000);
};

function PeerManager(config) {
  // extend defaults with config
  this.config = extend(true, config || {}, bitcoreDefaults);
  this.active = false;
  this.timer = null;

  this.peers = [];
  this.connections = [];
  this.isConnected = false;
  this.peerDiscovery = false;

  // Move these to the Node's settings object
  this.interval = 5000;
  this.minConnections = 8;
  this.minKnownPeers = 10;

  // keep track of tried seeds and results
  this.seeds = {
    resolved: [],
    failed: [],
    results: {}
  };
}

PeerManager.parent = imports.parent || require('events').EventEmitter;
PeerManager.Connection = Connection;

PeerManager.prototype.start = function() {
  this.active = true;
  if(!this.timer) {
    this.timer = setInterval(this.checkStatus.bind(this), this.interval);
  }
};

PeerManager.prototype.stop = function() {
  this.active = false;
  if(this.timer) {
    clearInterval(this.timer);
    this.timer = null;
  }
  for(var i=0; i<this.connections.length; i++) {
    this.connections[i].socket.end();
  };
};

PeerManager.prototype.addPeer = function(peer, port) {
  if(peer instanceof Peer) {
    this.peers.push(peer);
  } else if ("string" == typeof peer) {
    this.addPeer(new Peer(peer, port));
  } else {
    log.err('Node.addPeer(): Invalid value provided for peer',
                {val: peer});
    throw 'Node.addPeer(): Invalid value provided for peer.';
  }
};

PeerManager.prototype.checkStatus = function checkStatus() {
  // Make sure we are connected to all forcePeers
  if(this.peers.length) {
    var peerIndex = {};
    this.peers.forEach(function(peer) {
      peerIndex[peer.toString()] = peer;
    });

    // Ignore the ones we're already connected to
    this.connections.forEach(function(conn) {
      var peerName = conn.peer.toString();
      if("undefined" !== peerIndex[peerName]) {
        delete peerIndex[peerName];
      }
    });

    Object.keys(peerIndex).forEach(function(i) {
      this.connectTo(peerIndex[i]);
    }.bind(this));
  }
};

PeerManager.prototype.connectTo = function(peer) {
  log.info('connecting to '+peer);
  try {
    return this.addConnection(peer.createConnection(), peer);
  } catch (e) {
    log.err('creating connection',e);
    return null;
  }
};

PeerManager.prototype.addConnection = function(socketConn, peer) {
  var conn = new Connection(socketConn, peer, this.config);
  this.connections.push(conn);
  this.emit('connection', conn);

  conn.addListener('version', this.handleVersion.bind(this));
  conn.addListener('verack', this.handleReady.bind(this));
  conn.addListener('addr', this.handleAddr.bind(this));
  conn.addListener('getaddr', this.handleGetAddr.bind(this));
  conn.addListener('error', this.handleError.bind(this));
  conn.addListener('disconnect', this.handleDisconnect.bind(this));

  return conn;
};

PeerManager.prototype.handleVersion = function(e) {
  e.peer.version = e.message.version;
  e.peer.start_height = e.message.start_height;

  if (!e.conn.inbound) {
    // TODO: Advertise our address (if listening)
  }
  // Get recent addresses
  if(this.peerDiscovery &&
      (e.message.version >= 31402 || this.peers.length < 1000)) {
    e.conn.sendGetAddr();
    e.conn.getaddr = true;
  }
};

PeerManager.prototype.handleReady = function (e) {
  log.info('connected to '+e.conn.peer.host+':'+e.conn.peer.port);
  this.emit('connect', {
    pm: this,
    conn: e.conn,
    socket: e.socket,
    peer: e.peer
  });

  if(this.isConnected == false) {
    this.emit('netConnected', e);
    this.isConnected = true;
  }
};

PeerManager.prototype.handleAddr = function (e) {
  if(!this.peerDiscovery) return;

  var now = GetAdjustedTime();
  e.message.addrs.forEach(function (addr) {
    try {
      // In case of an invalid time, assume "5 days ago"
      if (addr.time <= 100000000 || addr.time > (now + 10 * 60)) {
        addr.time = now - 5 * 24 * 60 * 60;
      }
      var peer = new Peer(addr.ip, addr.port, addr.services);
      peer.lastSeen = addr.time;

      // TODO: Handle duplicate peers
      this.peers.push(peer);

      // TODO: Handle addr relay
    } catch(e) {
      log.warn("Invalid addr received: "+e.message);
    }
  }.bind(this));
  if (e.message.addrs.length < 1000 ) {
    e.conn.getaddr = false;
  }
};

PeerManager.prototype.handleGetAddr = function(e) {
  // TODO: Reply with addr message.
};

PeerManager.prototype.handleError = function(e) {
  log.err('unkown error with peer '+e.peer+' (disconnecting): '+e.err);
  this.handleDisconnect.apply(this, [].slice.call(arguments));
};

PeerManager.prototype.handleDisconnect = function(e) {
  log.info('disconnected from peer '+e.peer);
  var i = this.connections.indexOf(e.conn);
  if(i != -1) this.connections.splice(i, 1);

  if(!this.connections.length) {
    this.emit('netDisconnected');
    this.isConnected = false;
  }
};

PeerManager.prototype.getActiveConnection = function () {
  var activeConnections = this.connections.filter(function (conn) {
    return conn.active;
  });

  if (activeConnections.length) {
    var randomIndex = Math.floor(Math.random()*activeConnections.length);
    var candidate = activeConnections[randomIndex];
    if (candidate.socket.writable) {
      return candidate;
    } else {
      // Socket is not writable, remove it from active connections
      activeConnections.splice(randomIndex, 1);

      // Then try again
      // TODO: This causes an infinite recursion when all connections are dead,
      //       although it shouldn't.
      return this.getActiveConnection();
    }
  } else {
    return null;
  }
};

PeerManager.prototype.getActiveConnections = function () {
  return this.connections.slice(0);
};

PeerManager.prototype.discoverPeers = function(callback) {
  var self     = this;
  var async    = imports.async || require('async');
  var dns      = imports.dns || require('dns');
  var networks = imports.networks || require('../networks')[this.config.network];
  var seeds    = networks.dnsSeeds;

  var dnsExecutor = seeds.map(function(seed) {
    return function(done) {
      // have we already resolved this seed?
      if (~self.seeds.resolved.indexOf(seed)) {
        // if so, just pass back cached peer list
        return done(null, self.seeds.results[seed]);
      }
      // has this seed failed to resolve?
      if (~self.seeds.failed.indexOf(seed)) {
        // if so, pass back empty results
        return done(null, []);
      }
      // otherwise resolve the dns seed to get some peers
      log.info('resolving dns seed '+ seed);
      dns.resolve(seed, function(err, peers) {
        if (err) {
          log.err('failed to resolve dns seed '+ seed, err);
          self.seeds.failed.push(seed);
          return done(null, []);
        }
        log.info('found '+ peers.length + ' peers from ' + seed);
        self.seeds.resolved.push(seed);
        self.seeds.results[seed] = peers;
        return done(null, peers);
      });
    };
  });

  // try resolving all seeds
  async.parallel(dnsExecutor, function(err, results) {
    var peers = [];

    // consolidate all resolved peers into one list
    results.forEach(function(peerlist) {
      peers = peers.concat(peerlist);
    });

    // transform that list into a list of Peer instances
    peers = peers.map(function(ip) {
      return new Peer(ip, networks.defaultClientPort);
    });

    callback(null, peers);
  });
};

module.exports = require('soop')(PeerManager);
<commit_msg>fix networks var in discoverPeers() to match what might be imported<commit_after>var imports         = require('soop').imports();
var extend          = imports.extend || require('extend');
var log             = imports.log || require('../util/log');
var bitcoreDefaults = imports.config || require('../config');
var Connection      = imports.Connection || require ('./Connection');

var Peer            = imports.Peer || require('./Peer');

GetAdjustedTime = imports.GetAdjustedTime || function () {
  // TODO: Implement actual adjustment
  return Math.floor(new Date().getTime() / 1000);
};

function PeerManager(config) {
  // extend defaults with config
  this.config = extend(true, config || {}, bitcoreDefaults);
  this.active = false;
  this.timer = null;

  this.peers = [];
  this.connections = [];
  this.isConnected = false;
  this.peerDiscovery = false;

  // Move these to the Node's settings object
  this.interval = 5000;
  this.minConnections = 8;
  this.minKnownPeers = 10;

  // keep track of tried seeds and results
  this.seeds = {
    resolved: [],
    failed: [],
    results: {}
  };
}

PeerManager.parent = imports.parent || require('events').EventEmitter;
PeerManager.Connection = Connection;

PeerManager.prototype.start = function() {
  this.active = true;
  if(!this.timer) {
    this.timer = setInterval(this.checkStatus.bind(this), this.interval);
  }
};

PeerManager.prototype.stop = function() {
  this.active = false;
  if(this.timer) {
    clearInterval(this.timer);
    this.timer = null;
  }
  for(var i=0; i<this.connections.length; i++) {
    this.connections[i].socket.end();
  };
};

PeerManager.prototype.addPeer = function(peer, port) {
  if(peer instanceof Peer) {
    this.peers.push(peer);
  } else if ("string" == typeof peer) {
    this.addPeer(new Peer(peer, port));
  } else {
    log.err('Node.addPeer(): Invalid value provided for peer',
                {val: peer});
    throw 'Node.addPeer(): Invalid value provided for peer.';
  }
};

PeerManager.prototype.checkStatus = function checkStatus() {
  // Make sure we are connected to all forcePeers
  if(this.peers.length) {
    var peerIndex = {};
    this.peers.forEach(function(peer) {
      peerIndex[peer.toString()] = peer;
    });

    // Ignore the ones we're already connected to
    this.connections.forEach(function(conn) {
      var peerName = conn.peer.toString();
      if("undefined" !== peerIndex[peerName]) {
        delete peerIndex[peerName];
      }
    });

    Object.keys(peerIndex).forEach(function(i) {
      this.connectTo(peerIndex[i]);
    }.bind(this));
  }
};

PeerManager.prototype.connectTo = function(peer) {
  log.info('connecting to '+peer);
  try {
    return this.addConnection(peer.createConnection(), peer);
  } catch (e) {
    log.err('creating connection',e);
    return null;
  }
};

PeerManager.prototype.addConnection = function(socketConn, peer) {
  var conn = new Connection(socketConn, peer, this.config);
  this.connections.push(conn);
  this.emit('connection', conn);

  conn.addListener('version', this.handleVersion.bind(this));
  conn.addListener('verack', this.handleReady.bind(this));
  conn.addListener('addr', this.handleAddr.bind(this));
  conn.addListener('getaddr', this.handleGetAddr.bind(this));
  conn.addListener('error', this.handleError.bind(this));
  conn.addListener('disconnect', this.handleDisconnect.bind(this));

  return conn;
};

PeerManager.prototype.handleVersion = function(e) {
  e.peer.version = e.message.version;
  e.peer.start_height = e.message.start_height;

  if (!e.conn.inbound) {
    // TODO: Advertise our address (if listening)
  }
  // Get recent addresses
  if(this.peerDiscovery &&
      (e.message.version >= 31402 || this.peers.length < 1000)) {
    e.conn.sendGetAddr();
    e.conn.getaddr = true;
  }
};

PeerManager.prototype.handleReady = function (e) {
  log.info('connected to '+e.conn.peer.host+':'+e.conn.peer.port);
  this.emit('connect', {
    pm: this,
    conn: e.conn,
    socket: e.socket,
    peer: e.peer
  });

  if(this.isConnected == false) {
    this.emit('netConnected', e);
    this.isConnected = true;
  }
};

PeerManager.prototype.handleAddr = function (e) {
  if(!this.peerDiscovery) return;

  var now = GetAdjustedTime();
  e.message.addrs.forEach(function (addr) {
    try {
      // In case of an invalid time, assume "5 days ago"
      if (addr.time <= 100000000 || addr.time > (now + 10 * 60)) {
        addr.time = now - 5 * 24 * 60 * 60;
      }
      var peer = new Peer(addr.ip, addr.port, addr.services);
      peer.lastSeen = addr.time;

      // TODO: Handle duplicate peers
      this.peers.push(peer);

      // TODO: Handle addr relay
    } catch(e) {
      log.warn("Invalid addr received: "+e.message);
    }
  }.bind(this));
  if (e.message.addrs.length < 1000 ) {
    e.conn.getaddr = false;
  }
};

PeerManager.prototype.handleGetAddr = function(e) {
  // TODO: Reply with addr message.
};

PeerManager.prototype.handleError = function(e) {
  log.err('unkown error with peer '+e.peer+' (disconnecting): '+e.err);
  this.handleDisconnect.apply(this, [].slice.call(arguments));
};

PeerManager.prototype.handleDisconnect = function(e) {
  log.info('disconnected from peer '+e.peer);
  var i = this.connections.indexOf(e.conn);
  if(i != -1) this.connections.splice(i, 1);

  if(!this.connections.length) {
    this.emit('netDisconnected');
    this.isConnected = false;
  }
};

PeerManager.prototype.getActiveConnection = function () {
  var activeConnections = this.connections.filter(function (conn) {
    return conn.active;
  });

  if (activeConnections.length) {
    var randomIndex = Math.floor(Math.random()*activeConnections.length);
    var candidate = activeConnections[randomIndex];
    if (candidate.socket.writable) {
      return candidate;
    } else {
      // Socket is not writable, remove it from active connections
      activeConnections.splice(randomIndex, 1);

      // Then try again
      // TODO: This causes an infinite recursion when all connections are dead,
      //       although it shouldn't.
      return this.getActiveConnection();
    }
  } else {
    return null;
  }
};

PeerManager.prototype.getActiveConnections = function () {
  return this.connections.slice(0);
};

PeerManager.prototype.discoverPeers = function(callback) {
  var self     = this;
  var async    = imports.async || require('async');
  var dns      = imports.dns || require('dns');
  var networks = imports.networks || require('../networks');
  var seeds    = networks[self.config.network].dnsSeeds;

  var dnsExecutor = seeds.map(function(seed) {
    return function(done) {
      // have we already resolved this seed?
      if (~self.seeds.resolved.indexOf(seed)) {
        // if so, just pass back cached peer list
        return done(null, self.seeds.results[seed]);
      }
      // has this seed failed to resolve?
      if (~self.seeds.failed.indexOf(seed)) {
        // if so, pass back empty results
        return done(null, []);
      }
      // otherwise resolve the dns seed to get some peers
      log.info('resolving dns seed '+ seed);
      dns.resolve(seed, function(err, peers) {
        if (err) {
          log.err('failed to resolve dns seed '+ seed, err);
          self.seeds.failed.push(seed);
          return done(null, []);
        }
        log.info('found '+ peers.length + ' peers from ' + seed);
        self.seeds.resolved.push(seed);
        self.seeds.results[seed] = peers;
        return done(null, peers);
      });
    };
  });

  // try resolving all seeds
  async.parallel(dnsExecutor, function(err, results) {
    var peers = [];

    // consolidate all resolved peers into one list
    results.forEach(function(peerlist) {
      peers = peers.concat(peerlist);
    });

    // transform that list into a list of Peer instances
    peers = peers.map(function(ip) {
      return new Peer(ip, networks.defaultClientPort);
    });

    callback(null, peers);
  });
};

module.exports = require('soop')(PeerManager);
<|endoftext|>