module.exports = function (grunt) {
var paths = {
- dist: 'dist',
- jade: 'views/**/**/*.jade',
css: 'public/stylesheets/*.css',
scss: 'public/stylesheets/application.scss',
vendor: 'public/stylesheets/vendor',
js: 'public/javascripts/*.js',
- routes: 'controllers/**/*.js',
+ routes: './server/controllers/**/*.js',
main: './server.js',
browserified: 'public/javascripts/bundle.js',
img: 'public/images/*.{png,jpg,jpeg,gif,webp,svg}',
--- /dev/null
+;(function () {
+ 'use strict'
+
+ var $ = require('jquery')
+ require('blueimp-file-upload')
+
+ var WebTorrent = require('webtorrent')
+ var client = new WebTorrent({ dht: false })
+
+ var $content = $('#ajax_load')
+
+ // Webtorrent events
+ client.on('error', function (err) {
+ console.error(err)
+ })
+
+ client.on('warning', function (err) {
+ console.warning(err)
+ })
+
+ // Events of the panel
+ $('#panel_get_videos').on('click', function () {
+ getVideos()
+ })
+
+ $('#panel_upload_video').on('click', function () {
+ uploadVideo()
+ })
+
+ $('#panel_make_friends').on('click', function () {
+ makeFriends()
+ })
+
+ $('#panel_quit_friends').on('click', function () {
+ quitFriends()
+ })
+
+ $('#search-video').on('keyup', function (e) {
+ var search = $(this).val()
+
+ if (search === '') return
+
+ if (e.keyCode === 13) {
+ $.ajax({
+ url: '/api/v1/videos/search/' + search,
+ type: 'GET',
+ dataType: 'json',
+ success: function (videos) {
+ printVideos(videos)
+ }
+ })
+ }
+ })
+
+ // Join a new network
+ function makeFriends () {
+ $.ajax({
+ url: '/api/v1/pods/makefriends',
+ type: 'GET',
+ dataType: 'json',
+ statusCode: {
+ 409: function () {
+ alert('Already made friends.')
+ }
+ },
+ success: function () {
+ alert('Made friends!')
+ }
+ })
+ }
+
+ function quitFriends () {
+ $.ajax({
+ url: '/api/v1/pods/quitfriends',
+ type: 'GET',
+ dataType: 'json',
+ success: function () {
+ alert('Quit friends!')
+ }
+ })
+ }
+
+ function printVideos (videos) {
+ $content.empty()
+
+ if (videos.length === 0) {
+ $content.text('There is no videos.')
+ }
+
+ videos.forEach(function (video) {
+ var $video = $('<div></div>').addClass('video')
+
+ var $video_name = $('<span></span>').addClass('video_name').text(video.name)
+ var $video_pod = $('<span></span>').addClass('video_pod_url').text(video.podUrl)
+ var $header = $('<div></div>').append([ $video_name, $video_pod ])
+
+ if (video.namePath !== null) {
+ var $remove = $('<span></span>').addClass('span_action glyphicon glyphicon-remove')
+
+ // Remove the video
+ $remove.on('click', function () {
+ if (!confirm('Are you sure ?')) return
+
+ removeVideo(video)
+ })
+
+ $header.append($remove)
+ }
+
+ var $video_description = $('<div></div>').addClass('video_description').text(video.description)
+
+ // Get the video
+ $video_name.on('click', function () {
+ getVideo(video)
+ })
+
+ if (!video.magnetUri) {
+ $remove.css('display', 'none')
+ }
+
+ $video.append([ $header, $video_description ])
+ $content.append($video)
+ })
+ }
+
+ // Upload the video, the server will seed it
+ function uploadVideo () {
+ // Creating all the elements
+ var $video_label = $('<label></label>').attr('for', 'name').text('Video name')
+ var $video_name = $('<input></input>').addClass('form-control').attr({
+ name: 'name',
+ id: 'name'
+ })
+ var $video_block = $('<div></div>').addClass('form-group').append([ $video_label, $video_name ])
+
+ var $title = $('<h3></h3>').text('Upload a video')
+
+ var $button_text = $('<span></span>').text('Select the video...')
+ var $input_video = $('<input></input>').attr({
+ type: 'file',
+ name: 'input_video',
+ id: 'input_video'
+ })
+ var $button = $('<div></div>').addClass('btn btn-default btn-file').append([ $button_text, $input_video ])
+
+ var $description_label = $('<label></label>').attr('for', 'description').text('Description')
+ var $description_text = $('<textarea></textarea>').addClass('form-control').attr({
+ name: 'description',
+ id: 'description',
+ placeholder: 'Description...'
+ })
+ var $description = $('<div></div>').addClass('form-group').append([ $description_label, $description_text ])
+
+ var $bar = $('<progress></progress').attr('value', '0').css('display', 'none')
+ var $progress_bar = $('<div><div>').attr('id', 'progress').append($bar)
+
+ var $input_submit = $('<input></input>').addClass('btn btn-default').attr({
+ type: 'button',
+ value: 'Upload'
+ })
+
+ // JQuery plugin
+ var $form_video = $('<form></form>').append([ $video_block, $button, $progress_bar, $description, $input_submit ])
+ $form_video.fileupload({
+ singleFileUploads: true,
+ multipart: true,
+ url: '/api/v1/videos',
+ autoupload: false,
+ add: function (e, data) {
+ var $text = $('<span></span>').addClass('name_file').text(data['files'][0]['name'])
+ $text.insertAfter($button)
+ $input_submit.off('click').on('click', function () {
+ $bar.css('display', 'block')
+ data.formData = $form_video.serializeArray()
+ data.submit()
+ })
+ },
+ progressall: function (e, data) {
+ $bar.attr({
+ value: data.loaded,
+ max: data.total
+ })
+ },
+ done: function (e, data) {
+ // Print all the videos once it's finished
+ getVideos()
+ }
+ })
+
+ $content.empty()
+ $content.append([ $title, $form_video ])
+ }
+
+ // Print the list of all the videos
+ function getVideos () {
+ $.ajax({
+ url: '/api/v1/videos/',
+ dataType: 'json',
+ type: 'GET',
+ success: function (videos) {
+ printVideos(videos)
+ }
+ })
+ }
+
+ function removeVideo (video) {
+ $.ajax({
+ url: '/api/v1/videos/' + video._id,
+ type: 'DELETE',
+ success: function (response, status) {
+ getVideos()
+ }
+ })
+ }
+
+ // Get the video: add the torrent file and stream it into a video tag
+ function getVideo (video) {
+ var $waiting = $('<img></img>').addClass('center-block loading').attr('src', '/images/loading.gif')
+ $content.empty()
+ $content.append($waiting)
+
+ console.log('Getting ' + video)
+ client.add(video.magnetUri, function (torrent) {
+ var $embed = $('<div></div>').addClass('embed-responsive embed-responsive-16by9')
+
+ $content.empty()
+ $content.append($embed)
+
+ // Got torrent metadata!
+ console.log('Torrent info hash:', torrent.infoHash)
+
+ // Let's say the first file is a webm (vp8) or mp4 (h264) video...
+ var file = torrent.files[0]
+
+ file.appendTo($embed.get(0), function (err) {
+ if (err) {
+ alert('Cannot append the file.')
+ console.error(err)
+ }
+ })
+ })
+ }
+
+ getVideos()
+})()
--- /dev/null
+$icon-font-path: "/stylesheets/vendor/fonts/bootstrap/";
+
+@import "bootstrap-variables";
+@import "_bootstrap";
+@import "base";
+@import "index";
\ No newline at end of file
--- /dev/null
+body {
+ padding: 20px;
+}
+
+footer {
+ border-top: 1px solid rgba(0, 0, 0, 0.2);
+ padding-top: 10px;
+ text-align: center;
+ font-size: small;
+}
+
+.search-group {
+ .search-btn {
+ position: relative;
+ left: -40px;
+ top: 0;
+
+ &:hover { text-decoration: none; }
+ }
+}
\ No newline at end of file
--- /dev/null
+// Override Bootstrap variables here (defaults from bootstrap-sass v3.3.6):
+
+//
+// Variables
+// --------------------------------------------------
+
+
+//== Colors
+//
+//## Gray and brand colors for use across Bootstrap.
+
+// $gray-base: #000
+// $gray-darker: lighten($gray-base, 13.5%) // #222
+// $gray-dark: lighten($gray-base, 20%) // #333
+// $gray: lighten($gray-base, 33.5%) // #555
+// $gray-light: lighten($gray-base, 46.7%) // #777
+// $gray-lighter: lighten($gray-base, 93.5%) // #eee
+
+// $brand-primary: darken(#428bca, 6.5%) // #337ab7
+// $brand-success: #5cb85c
+// $brand-info: #5bc0de
+// $brand-warning: #f0ad4e
+// $brand-danger: #d9534f
+
+
+//== Scaffolding
+//
+//## Settings for some of the most global styles.
+
+//** Background color for `<body>`.
+// $body-bg: #fff
+//** Global text color on `<body>`.
+// $text-color: $gray-dark
+
+//** Global textual link color.
+// $link-color: $brand-primary
+//** Link hover color set via `darken()` function.
+// $link-hover-color: darken($link-color, 15%)
+//** Link hover decoration.
+// $link-hover-decoration: underline
+
+
+//== Typography
+//
+//## Font, line-height, and color for body text, headings, and more.
+
+// $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif
+// $font-family-serif: Georgia, "Times New Roman", Times, serif
+//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
+// $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace
+// $font-family-base: $font-family-sans-serif
+
+// $font-size-base: 14px
+// $font-size-large: ceil(($font-size-base * 1.25)) // ~18px
+// $font-size-small: ceil(($font-size-base * 0.85)) // ~12px
+
+// $font-size-h1: floor(($font-size-base * 2.6)) // ~36px
+// $font-size-h2: floor(($font-size-base * 2.15)) // ~30px
+// $font-size-h3: ceil(($font-size-base * 1.7)) // ~24px
+// $font-size-h4: ceil(($font-size-base * 1.25)) // ~18px
+// $font-size-h5: $font-size-base
+// $font-size-h6: ceil(($font-size-base * 0.85)) // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+// $line-height-base: 1.428571429 // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+// $line-height-computed: floor(($font-size-base * $line-height-base)) // ~20px
+
+//** By default, this inherits from the `<body>`.
+// $headings-font-family: inherit
+// $headings-font-weight: 500
+// $headings-line-height: 1.1
+// $headings-color: inherit
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+
+// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
+// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
+// $icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/")
+
+//** File name for all font files.
+// $icon-font-name: "glyphicons-halflings-regular"
+//** Element ID within SVG icon file.
+// $icon-font-svg-id: "glyphicons_halflingsregular"
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+// $padding-base-vertical: 6px
+// $padding-base-horizontal: 12px
+
+// $padding-large-vertical: 10px
+// $padding-large-horizontal: 16px
+
+// $padding-small-vertical: 5px
+// $padding-small-horizontal: 10px
+
+// $padding-xs-vertical: 1px
+// $padding-xs-horizontal: 5px
+
+// $line-height-large: 1.3333333 // extra decimals for Win 8.1 Chrome
+// $line-height-small: 1.5
+
+$border-radius-base: 0;
+$border-radius-large: 0;
+$border-radius-small: 0;
+
+//** Global color for active items (e.g., navs or dropdowns).
+// $component-active-color: #fff
+//** Global background color for active items (e.g., navs or dropdowns).
+// $component-active-bg: $brand-primary
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+// $caret-width-base: 4px
+//** Carets increase slightly in size for larger components.
+// $caret-width-large: 5px
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for `<th>`s and `<td>`s.
+// $table-cell-padding: 8px
+//** Padding for cells in `.table-condensed`.
+// $table-condensed-cell-padding: 5px
+
+//** Default background color used for all tables.
+// $table-bg: transparent
+//** Background color used for `.table-striped`.
+// $table-bg-accent: #f9f9f9
+//** Background color used for `.table-hover`.
+// $table-bg-hover: #f5f5f5
+// $table-bg-active: $table-bg-hover
+
+//** Border color for table and cell borders.
+// $table-border-color: #ddd
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+// $btn-font-weight: normal
+
+// $btn-default-color: #333
+// $btn-default-bg: #fff
+// $btn-default-border: #ccc
+
+// $btn-primary-color: #fff
+// $btn-primary-bg: $brand-primary
+// $btn-primary-border: darken($btn-primary-bg, 5%)
+
+// $btn-success-color: #fff
+// $btn-success-bg: $brand-success
+// $btn-success-border: darken($btn-success-bg, 5%)
+
+// $btn-info-color: #fff
+// $btn-info-bg: $brand-info
+// $btn-info-border: darken($btn-info-bg, 5%)
+
+// $btn-warning-color: #fff
+// $btn-warning-bg: $brand-warning
+// $btn-warning-border: darken($btn-warning-bg, 5%)
+
+// $btn-danger-color: #fff
+// $btn-danger-bg: $brand-danger
+// $btn-danger-border: darken($btn-danger-bg, 5%)
+
+// $btn-link-disabled-color: $gray-light
+
+// Allows for customizing button radius independently from global border radius
+// $btn-border-radius-base: $border-radius-base
+// $btn-border-radius-large: $border-radius-large
+// $btn-border-radius-small: $border-radius-small
+
+
+//== Forms
+//
+//##
+
+//** `<input>` background color
+// $input-bg: #fff
+//** `<input disabled>` background color
+// $input-bg-disabled: $gray-lighter
+
+//** Text color for `<input>`s
+// $input-color: $gray
+//** `<input>` border color
+// $input-border: #ccc
+
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
+// $input-border-radius: $border-radius-base
+//** Large `.form-control` border radius
+// $input-border-radius-large: $border-radius-large
+//** Small `.form-control` border radius
+// $input-border-radius-small: $border-radius-small
+
+//** Border color for inputs on focus
+// $input-border-focus: #66afe9
+
+//** Placeholder text color
+// $input-color-placeholder: #999
+
+//** Default `.form-control` height
+// $input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2)
+//** Large `.form-control` height
+// $input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2)
+//** Small `.form-control` height
+// $input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2)
+
+//** `.form-group` margin
+// $form-group-margin-bottom: 15px
+
+// $legend-color: $gray-dark
+// $legend-border-color: #e5e5e5
+
+//** Background color for textual input addons
+// $input-group-addon-bg: $gray-lighter
+//** Border color for textual input addons
+// $input-group-addon-border-color: $input-border
+
+//** Disabled cursor for form controls and buttons.
+// $cursor-disabled: not-allowed
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+// $dropdown-bg: #fff
+//** Dropdown menu `border-color`.
+// $dropdown-border: rgba(0,0,0,.15)
+//** Dropdown menu `border-color` **for IE8**.
+// $dropdown-fallback-border: #ccc
+//** Divider color for between dropdown items.
+// $dropdown-divider-bg: #e5e5e5
+
+//** Dropdown link text color.
+// $dropdown-link-color: $gray-dark
+//** Hover color for dropdown links.
+// $dropdown-link-hover-color: darken($gray-dark, 5%)
+//** Hover background for dropdown links.
+// $dropdown-link-hover-bg: #f5f5f5
+
+//** Active dropdown menu item text color.
+// $dropdown-link-active-color: $component-active-color
+//** Active dropdown menu item background color.
+// $dropdown-link-active-bg: $component-active-bg
+
+//** Disabled dropdown menu item background color.
+// $dropdown-link-disabled-color: $gray-light
+
+//** Text color for headers within dropdown menus.
+// $dropdown-header-color: $gray-light
+
+//** Deprecated `$dropdown-caret-color` as of v3.1.0
+// $dropdown-caret-color: #000
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+// $zindex-navbar: 1000
+// $zindex-dropdown: 1000
+// $zindex-popover: 1060
+// $zindex-tooltip: 1070
+// $zindex-navbar-fixed: 1030
+// $zindex-modal-background: 1040
+// $zindex-modal: 1050
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+// $screen-xs: 480px
+//** Deprecated `$screen-xs-min` as of v3.2.0
+// $screen-xs-min: $screen-xs
+//** Deprecated `$screen-phone` as of v3.0.1
+// $screen-phone: $screen-xs-min
+
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+// $screen-sm: 768px
+// $screen-sm-min: $screen-sm
+//** Deprecated `$screen-tablet` as of v3.0.1
+// $screen-tablet: $screen-sm-min
+
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+// $screen-md: 992px
+// $screen-md-min: $screen-md
+//** Deprecated `$screen-desktop` as of v3.0.1
+// $screen-desktop: $screen-md-min
+
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+// $screen-lg: 1200px
+// $screen-lg-min: $screen-lg
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+// $screen-lg-desktop: $screen-lg-min
+
+// So media queries don't overlap when required, provide a maximum
+// $screen-xs-max: ($screen-sm-min - 1)
+// $screen-sm-max: ($screen-md-min - 1)
+// $screen-md-max: ($screen-lg-min - 1)
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+// $grid-columns: 12
+//** Padding between columns. Gets divided in half for the left and right.
+// $grid-gutter-width: 30px
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+// $grid-float-breakpoint: $screen-sm-min
+//** Point at which the navbar begins collapsing.
+// $grid-float-breakpoint-max: ($grid-float-breakpoint - 1)
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+// $container-tablet: (720px + $grid-gutter-width)
+//** For `$screen-sm-min` and up.
+// $container-sm: $container-tablet
+
+// Medium screen / desktop
+// $container-desktop: (940px + $grid-gutter-width)
+//** For `$screen-md-min` and up.
+// $container-md: $container-desktop
+
+// Large screen / wide desktop
+// $container-large-desktop: (1140px + $grid-gutter-width)
+//** For `$screen-lg-min` and up.
+// $container-lg: $container-large-desktop
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+// $navbar-height: 50px
+// $navbar-margin-bottom: $line-height-computed
+// $navbar-border-radius: $border-radius-base
+// $navbar-padding-horizontal: floor(($grid-gutter-width / 2))
+// $navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2)
+// $navbar-collapse-max-height: 340px
+
+// $navbar-default-color: #777
+// $navbar-default-bg: #f8f8f8
+// $navbar-default-border: darken($navbar-default-bg, 6.5%)
+
+// Navbar links
+// $navbar-default-link-color: #777
+// $navbar-default-link-hover-color: #333
+// $navbar-default-link-hover-bg: transparent
+// $navbar-default-link-active-color: #555
+// $navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%)
+// $navbar-default-link-disabled-color: #ccc
+// $navbar-default-link-disabled-bg: transparent
+
+// Navbar brand label
+// $navbar-default-brand-color: $navbar-default-link-color
+// $navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%)
+// $navbar-default-brand-hover-bg: transparent
+
+// Navbar toggle
+// $navbar-default-toggle-hover-bg: #ddd
+// $navbar-default-toggle-icon-bar-bg: #888
+// $navbar-default-toggle-border-color: #ddd
+
+
+//=== Inverted navbar
+// Reset inverted navbar basics
+// $navbar-inverse-color: lighten($gray-light, 15%)
+// $navbar-inverse-bg: #222
+// $navbar-inverse-border: darken($navbar-inverse-bg, 10%)
+
+// Inverted navbar links
+// $navbar-inverse-link-color: lighten($gray-light, 15%)
+// $navbar-inverse-link-hover-color: #fff
+// $navbar-inverse-link-hover-bg: transparent
+// $navbar-inverse-link-active-color: $navbar-inverse-link-hover-color
+// $navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%)
+// $navbar-inverse-link-disabled-color: #444
+// $navbar-inverse-link-disabled-bg: transparent
+
+// Inverted navbar brand label
+// $navbar-inverse-brand-color: $navbar-inverse-link-color
+// $navbar-inverse-brand-hover-color: #fff
+// $navbar-inverse-brand-hover-bg: transparent
+
+// Inverted navbar toggle
+// $navbar-inverse-toggle-hover-bg: #333
+// $navbar-inverse-toggle-icon-bar-bg: #fff
+// $navbar-inverse-toggle-border-color: #333
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+// $nav-link-padding: 10px 15px
+// $nav-link-hover-bg: $gray-lighter
+
+// $nav-disabled-link-color: $gray-light
+// $nav-disabled-link-hover-color: $gray-light
+
+//== Tabs
+// $nav-tabs-border-color: #ddd
+
+// $nav-tabs-link-hover-border-color: $gray-lighter
+
+// $nav-tabs-active-link-hover-bg: $body-bg
+// $nav-tabs-active-link-hover-color: $gray
+// $nav-tabs-active-link-hover-border-color: #ddd
+
+// $nav-tabs-justified-link-border-color: #ddd
+// $nav-tabs-justified-active-link-border-color: $body-bg
+
+//== Pills
+// $nav-pills-border-radius: $border-radius-base
+// $nav-pills-active-link-hover-bg: $component-active-bg
+// $nav-pills-active-link-hover-color: $component-active-color
+
+
+//== Pagination
+//
+//##
+
+// $pagination-color: $link-color
+// $pagination-bg: #fff
+// $pagination-border: #ddd
+
+// $pagination-hover-color: $link-hover-color
+// $pagination-hover-bg: $gray-lighter
+// $pagination-hover-border: #ddd
+
+// $pagination-active-color: #fff
+// $pagination-active-bg: $brand-primary
+// $pagination-active-border: $brand-primary
+
+// $pagination-disabled-color: $gray-light
+// $pagination-disabled-bg: #fff
+// $pagination-disabled-border: #ddd
+
+
+//== Pager
+//
+//##
+
+// $pager-bg: $pagination-bg
+// $pager-border: $pagination-border
+// $pager-border-radius: 15px
+
+// $pager-hover-bg: $pagination-hover-bg
+
+// $pager-active-bg: $pagination-active-bg
+// $pager-active-color: $pagination-active-color
+
+// $pager-disabled-color: $pagination-disabled-color
+
+
+//== Jumbotron
+//
+//##
+
+// $jumbotron-padding: 30px
+// $jumbotron-color: inherit
+// $jumbotron-bg: $gray-lighter
+// $jumbotron-heading-color: inherit
+// $jumbotron-font-size: ceil(($font-size-base * 1.5))
+// $jumbotron-heading-font-size: ceil(($font-size-base * 4.5))
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+// $state-success-text: #3c763d
+// $state-success-bg: #dff0d8
+// $state-success-border: darken(adjust-hue($state-success-bg, -10), 5%)
+
+// $state-info-text: #31708f
+// $state-info-bg: #d9edf7
+// $state-info-border: darken(adjust-hue($state-info-bg, -10), 7%)
+
+// $state-warning-text: #8a6d3b
+// $state-warning-bg: #fcf8e3
+// $state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%)
+
+// $state-danger-text: #a94442
+// $state-danger-bg: #f2dede
+// $state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%)
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+// $tooltip-max-width: 200px
+//** Tooltip text color
+// $tooltip-color: #fff
+//** Tooltip background color
+// $tooltip-bg: #000
+// $tooltip-opacity: .9
+
+//** Tooltip arrow width
+// $tooltip-arrow-width: 5px
+//** Tooltip arrow color
+// $tooltip-arrow-color: $tooltip-bg
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+// $popover-bg: #fff
+//** Popover maximum width
+// $popover-max-width: 276px
+//** Popover border color
+// $popover-border-color: rgba(0,0,0,.2)
+//** Popover fallback border color
+// $popover-fallback-border-color: #ccc
+
+//** Popover title background color
+// $popover-title-bg: darken($popover-bg, 3%)
+
+//** Popover arrow width
+// $popover-arrow-width: 10px
+//** Popover arrow color
+// $popover-arrow-color: $popover-bg
+
+//** Popover outer arrow width
+// $popover-arrow-outer-width: ($popover-arrow-width + 1)
+//** Popover outer arrow color
+// $popover-arrow-outer-color: fade_in($popover-border-color, 0.05)
+//** Popover outer arrow fallback color
+// $popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%)
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+// $label-default-bg: $gray-light
+//** Primary label background color
+// $label-primary-bg: $brand-primary
+//** Success label background color
+// $label-success-bg: $brand-success
+//** Info label background color
+// $label-info-bg: $brand-info
+//** Warning label background color
+// $label-warning-bg: $brand-warning
+//** Danger label background color
+// $label-danger-bg: $brand-danger
+
+//** Default label text color
+// $label-color: #fff
+//** Default text color of a linked label
+// $label-link-hover-color: #fff
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+// $modal-inner-padding: 15px
+
+//** Padding applied to the modal title
+// $modal-title-padding: 15px
+//** Modal title line-height
+// $modal-title-line-height: $line-height-base
+
+//** Background color of modal content area
+// $modal-content-bg: #fff
+//** Modal content border color
+// $modal-content-border-color: rgba(0,0,0,.2)
+//** Modal content border color **for IE8**
+// $modal-content-fallback-border-color: #999
+
+//** Modal backdrop background color
+// $modal-backdrop-bg: #000
+//** Modal backdrop opacity
+// $modal-backdrop-opacity: .5
+//** Modal header border color
+// $modal-header-border-color: #e5e5e5
+//** Modal footer border color
+// $modal-footer-border-color: $modal-header-border-color
+
+// $modal-lg: 900px
+// $modal-md: 600px
+// $modal-sm: 300px
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+// $alert-padding: 15px
+// $alert-border-radius: $border-radius-base
+// $alert-link-font-weight: bold
+
+// $alert-success-bg: $state-success-bg
+// $alert-success-text: $state-success-text
+// $alert-success-border: $state-success-border
+
+// $alert-info-bg: $state-info-bg
+// $alert-info-text: $state-info-text
+// $alert-info-border: $state-info-border
+
+// $alert-warning-bg: $state-warning-bg
+// $alert-warning-text: $state-warning-text
+// $alert-warning-border: $state-warning-border
+
+// $alert-danger-bg: $state-danger-bg
+// $alert-danger-text: $state-danger-text
+// $alert-danger-border: $state-danger-border
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+// $progress-bg: #f5f5f5
+//** Progress bar text color
+// $progress-bar-color: #fff
+//** Variable for setting rounded corners on progress bar.
+// $progress-border-radius: $border-radius-base
+
+//** Default progress bar color
+// $progress-bar-bg: $brand-primary
+//** Success progress bar color
+// $progress-bar-success-bg: $brand-success
+//** Warning progress bar color
+// $progress-bar-warning-bg: $brand-warning
+//** Danger progress bar color
+// $progress-bar-danger-bg: $brand-danger
+//** Info progress bar color
+// $progress-bar-info-bg: $brand-info
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+// $list-group-bg: #fff
+//** `.list-group-item` border color
+// $list-group-border: #ddd
+//** List group border radius
+// $list-group-border-radius: $border-radius-base
+
+//** Background color of single list items on hover
+// $list-group-hover-bg: #f5f5f5
+//** Text color of active list items
+// $list-group-active-color: $component-active-color
+//** Background color of active list items
+// $list-group-active-bg: $component-active-bg
+//** Border color of active list elements
+// $list-group-active-border: $list-group-active-bg
+//** Text color for content within active list items
+// $list-group-active-text-color: lighten($list-group-active-bg, 40%)
+
+//** Text color of disabled list items
+// $list-group-disabled-color: $gray-light
+//** Background color of disabled list items
+// $list-group-disabled-bg: $gray-lighter
+//** Text color for content within disabled list items
+// $list-group-disabled-text-color: $list-group-disabled-color
+
+// $list-group-link-color: #555
+// $list-group-link-hover-color: $list-group-link-color
+// $list-group-link-heading-color: #333
+
+
+//== Panels
+//
+//##
+
+// $panel-bg: #fff
+// $panel-body-padding: 15px
+// $panel-heading-padding: 10px 15px
+// $panel-footer-padding: $panel-heading-padding
+// $panel-border-radius: $border-radius-base
+
+//** Border color for elements within panels
+// $panel-inner-border: #ddd
+// $panel-footer-bg: #f5f5f5
+
+// $panel-default-text: $gray-dark
+// $panel-default-border: #ddd
+// $panel-default-heading-bg: #f5f5f5
+
+// $panel-primary-text: #fff
+// $panel-primary-border: $brand-primary
+// $panel-primary-heading-bg: $brand-primary
+
+// $panel-success-text: $state-success-text
+// $panel-success-border: $state-success-border
+// $panel-success-heading-bg: $state-success-bg
+
+// $panel-info-text: $state-info-text
+// $panel-info-border: $state-info-border
+// $panel-info-heading-bg: $state-info-bg
+
+// $panel-warning-text: $state-warning-text
+// $panel-warning-border: $state-warning-border
+// $panel-warning-heading-bg: $state-warning-bg
+
+// $panel-danger-text: $state-danger-text
+// $panel-danger-border: $state-danger-border
+// $panel-danger-heading-bg: $state-danger-bg
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+// $thumbnail-padding: 4px
+//** Thumbnail background color
+// $thumbnail-bg: $body-bg
+//** Thumbnail border color
+// $thumbnail-border: #ddd
+//** Thumbnail border radius
+// $thumbnail-border-radius: $border-radius-base
+
+//** Custom text color for thumbnail captions
+// $thumbnail-caption-color: $text-color
+//** Padding around the thumbnail caption
+// $thumbnail-caption-padding: 9px
+
+
+//== Wells
+//
+//##
+
+// $well-bg: #f5f5f5
+// $well-border: darken($well-bg, 7%)
+
+
+//== Badges
+//
+//##
+
+// $badge-color: #fff
+//** Linked badge text color on hover
+// $badge-link-hover-color: #fff
+// $badge-bg: $gray-light
+
+//** Badge text color in active nav link
+// $badge-active-color: $link-color
+//** Badge background color in active nav link
+// $badge-active-bg: #fff
+
+// $badge-font-weight: bold
+// $badge-line-height: 1
+// $badge-border-radius: 10px
+
+
+//== Breadcrumbs
+//
+//##
+
+// $breadcrumb-padding-vertical: 8px
+// $breadcrumb-padding-horizontal: 15px
+//** Breadcrumb background color
+// $breadcrumb-bg: #f5f5f5
+//** Breadcrumb text color
+// $breadcrumb-color: #ccc
+//** Text color of current page in the breadcrumb
+// $breadcrumb-active-color: $gray-light
+//** Textual separator for between breadcrumb elements
+// $breadcrumb-separator: "/"
+
+
+//== Carousel
+//
+//##
+
+// $carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6)
+
+// $carousel-control-color: #fff
+// $carousel-control-width: 15%
+// $carousel-control-opacity: .5
+// $carousel-control-font-size: 20px
+
+// $carousel-indicator-active-bg: #fff
+// $carousel-indicator-border-color: #fff
+
+// $carousel-caption-color: #fff
+
+
+//== Close
+//
+//##
+
+// $close-font-weight: bold
+// $close-color: #000
+// $close-text-shadow: 0 1px 0 #fff
+
+
+//== Code
+//
+//##
+
+// $code-color: #c7254e
+// $code-bg: #f9f2f4
+
+// $kbd-color: #fff
+// $kbd-bg: #333
+
+// $pre-bg: #f5f5f5
+// $pre-color: $gray-dark
+// $pre-border-color: #ccc
+// $pre-scrollable-max-height: 340px
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+// $component-offset-horizontal: 180px
+//** Text muted color
+// $text-muted: $gray-light
+//** Abbreviations and acronyms border color
+// $abbr-border-color: $gray-light
+//** Headings small color
+// $headings-small-color: $gray-light
+//** Blockquote small color
+// $blockquote-small-color: $gray-light
+//** Blockquote font size
+// $blockquote-font-size: ($font-size-base * 1.25)
+//** Blockquote border color
+// $blockquote-border-color: $gray-lighter
+//** Page header border color
+// $page-header-border-color: $gray-lighter
+//** Width of horizontal description list titles
+// $dl-horizontal-offset: $component-offset-horizontal
+//** Point at which .dl-horizontal becomes horizontal
+// $dl-horizontal-breakpoint: $grid-float-breakpoint
+//** Horizontal line color.
+// $hr-border: $gray-lighter
--- /dev/null
+.span_action {
+ margin: 5px;
+ cursor: pointer;
+}
+
+header div {
+ height: 50px;
+ line-height: 25px;
+ margin-bottom: 50px;
+}
+
+menu {
+ margin-right: 20px;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+menu .panel_button {
+ margin: 8px;
+ cursor: pointer;
+ transition: margin 0.2s;
+}
+
+menu .panel_button:hover {
+ margin-left: 15px;
+}
+
+menu .glyphicon {
+ margin: 5px;
+}
+
+#ajax_load {
+ min-height: 500px;
+}
+
+.loading {
+ display: inline-block;
+ margin-top: 100px;
+}
+
+.video {
+ margin-bottom: 10px;
+ transition: margin 0.5s ease;
+}
+
+.video:hover {
+ margin-left: 5px;
+}
+
+.video_name {
+ cursor: pointer;
+ margin-right: 5px;
+}
+
+.video_pod_url {
+ font-size: small;
+ color: rgba(0, 0, 0, 0.5);
+}
+
+.video_description {
+ font-size: small;
+ font-style: italic;
+ margin-left: 7px;
+}
+
+.btn-file {
+ position: relative;
+ overflow: hidden;
+}
+.btn-file input[type=file] {
+ position: absolute;
+ top: 0;
+ right: 0;
+ min-width: 100%;
+ min-height: 100%;
+ font-size: 100px;
+ text-align: right;
+ filter: alpha(opacity=0);
+ opacity: 0;
+ outline: none;
+ background: white;
+ cursor: inherit;
+ display: block;
+}
+
+.name_file {
+ display: inline-block;
+ margin-left: 10px;
+}
\ No newline at end of file
+++ /dev/null
-'use strict'
-
-var express = require('express')
-
-var router = express.Router()
-
-var podsController = require('./pods')
-var remoteVideosController = require('./remoteVideos')
-var videosController = require('./videos')
-
-router.use('/pods', podsController)
-router.use('/remotevideos', remoteVideosController)
-router.use('/videos', videosController)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
+++ /dev/null
-'use strict'
-
-var express = require('express')
-var fs = require('fs')
-
-var logger = require('../../../helpers/logger')
-var friends = require('../../../lib/friends')
-var middleware = require('../../../middlewares')
-var cacheMiddleware = middleware.cache
-var peertubeCrypto = require('../../../helpers/peertubeCrypto')
-var Pods = require('../../../models/pods')
-var reqValidator = middleware.reqValidators.pods
-var secureMiddleware = middleware.secure
-var secureRequest = middleware.reqValidators.remote.secureRequest
-var Videos = require('../../../models/videos')
-
-var router = express.Router()
-
-router.get('/', cacheMiddleware.cache(false), listPods)
-router.post('/', reqValidator.podsAdd, cacheMiddleware.cache(false), addPods)
-router.get('/makefriends', reqValidator.makeFriends, cacheMiddleware.cache(false), makeFriends)
-router.get('/quitfriends', cacheMiddleware.cache(false), quitFriends)
-// Post because this is a secured request
-router.post('/remove', secureRequest, secureMiddleware.decryptBody, removePods)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function addPods (req, res, next) {
- var informations = req.body.data
- Pods.add(informations, function (err) {
- if (err) return next(err)
-
- Videos.addRemotes(informations.videos)
-
- fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
- if (err) {
- logger.error('Cannot read cert file.')
- return next(err)
- }
-
- Videos.listOwned(function (err, videos_list) {
- if (err) {
- logger.error('Cannot get the list of owned videos.')
- return next(err)
- }
-
- res.json({ cert: cert, videos: videos_list })
- })
- })
- })
-}
-
-function listPods (req, res, next) {
- Pods.list(function (err, pods_list) {
- if (err) return next(err)
-
- res.json(pods_list)
- })
-}
-
-function makeFriends (req, res, next) {
- friends.makeFriends(function (err) {
- if (err) return next(err)
-
- res.sendStatus(204)
- })
-}
-
-function removePods (req, res, next) {
- var url = req.body.signature.url
- Pods.remove(url, function (err) {
- if (err) return next(err)
-
- Videos.removeAllRemotesOf(url, function (err) {
- if (err) logger.error('Cannot remove all remote videos of %s.', url)
- else logger.info('%s pod removed.', url)
-
- res.sendStatus(204)
- })
- })
-}
-
-function quitFriends (req, res, next) {
- friends.quitFriends(function (err) {
- if (err) return next(err)
-
- res.sendStatus(204)
- })
-}
+++ /dev/null
-'use strict'
-
-var express = require('express')
-var pluck = require('lodash-node/compat/collection/pluck')
-
-var middleware = require('../../../middlewares')
-var secureMiddleware = middleware.secure
-var cacheMiddleware = middleware.cache
-var reqValidator = middleware.reqValidators.remote
-var videos = require('../../../models/videos')
-
-var router = express.Router()
-
-router.post('/add',
- reqValidator.secureRequest,
- secureMiddleware.decryptBody,
- reqValidator.remoteVideosAdd,
- cacheMiddleware.cache(false),
- addRemoteVideos
-)
-
-router.post('/remove',
- reqValidator.secureRequest,
- secureMiddleware.decryptBody,
- reqValidator.remoteVideosRemove,
- cacheMiddleware.cache(false),
- removeRemoteVideo
-)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function addRemoteVideos (req, res, next) {
- videos.addRemotes(req.body.data, function (err, videos) {
- if (err) return next(err)
-
- res.json(videos)
- })
-}
-
-function removeRemoteVideo (req, res, next) {
- var url = req.body.signature.url
- var magnetUris = pluck(req.body.data, 'magnetUri')
-
- videos.removeRemotesOfByMagnetUris(url, magnetUris, function (err) {
- if (err) return next(err)
-
- res.sendStatus(204)
- })
-}
+++ /dev/null
-'use strict'
-
-var config = require('config')
-var crypto = require('crypto')
-var express = require('express')
-var multer = require('multer')
-
-var logger = require('../../../helpers/logger')
-var friends = require('../../../lib/friends')
-var middleware = require('../../../middlewares')
-var cacheMiddleware = middleware.cache
-var reqValidator = middleware.reqValidators.videos
-var Videos = require('../../../models/videos') // model
-var videos = require('../../../lib/videos')
-var webtorrent = require('../../../lib/webtorrent')
-
-var router = express.Router()
-var uploads = config.get('storage.uploads')
-
-// multer configuration
-var storage = multer.diskStorage({
- destination: function (req, file, cb) {
- cb(null, uploads)
- },
-
- filename: function (req, file, cb) {
- var extension = ''
- if (file.mimetype === 'video/webm') extension = 'webm'
- else if (file.mimetype === 'video/mp4') extension = 'mp4'
- else if (file.mimetype === 'video/ogg') extension = 'ogv'
- crypto.pseudoRandomBytes(16, function (err, raw) {
- var fieldname = err ? undefined : raw.toString('hex')
- cb(null, fieldname + '.' + extension)
- })
- }
-})
-
-var reqFiles = multer({ storage: storage }).fields([{ name: 'input_video', maxCount: 1 }])
-
-router.get('/', cacheMiddleware.cache(false), listVideos)
-router.post('/', reqFiles, reqValidator.videosAdd, cacheMiddleware.cache(false), addVideo)
-router.get('/:id', reqValidator.videosGet, cacheMiddleware.cache(false), getVideos)
-router.delete('/:id', reqValidator.videosRemove, cacheMiddleware.cache(false), removeVideo)
-router.get('/search/:name', reqValidator.videosSearch, cacheMiddleware.cache(false), searchVideos)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function addVideo (req, res, next) {
- var video_file = req.files.input_video[0]
- var video_infos = req.body
-
- videos.seed(video_file.path, function (err, torrent) {
- if (err) {
- logger.error('Cannot seed this video.')
- return next(err)
- }
-
- var video_data = {
- name: video_infos.name,
- namePath: video_file.filename,
- description: video_infos.description,
- magnetUri: torrent.magnetURI
- }
-
- Videos.add(video_data, function (err) {
- if (err) {
- // TODO unseed the video
- logger.error('Cannot insert this video in the database.')
- return next(err)
- }
-
- // Now we'll add the video's meta data to our friends
- friends.addVideoToFriends(video_data)
-
- // TODO : include Location of the new video
- res.sendStatus(201)
- })
- })
-}
-
-function getVideos (req, res, next) {
- Videos.get(req.params.id, function (err, video) {
- if (err) return next(err)
-
- if (video === null) {
- return res.sendStatus(404)
- }
-
- res.json(video)
- })
-}
-
-function listVideos (req, res, next) {
- Videos.list(function (err, videos_list) {
- if (err) return next(err)
-
- res.json(videos_list)
- })
-}
-
-function removeVideo (req, res, next) {
- var video_id = req.params.id
- Videos.get(video_id, function (err, video) {
- if (err) return next(err)
-
- removeTorrent(video.magnetUri, function () {
- Videos.removeOwned(req.params.id, function (err) {
- if (err) return next(err)
-
- var params = {
- name: video.name,
- magnetUri: video.magnetUri
- }
-
- friends.removeVideoToFriends(params)
- res.sendStatus(204)
- })
- })
- })
-}
-
-function searchVideos (req, res, next) {
- Videos.search(req.params.name, function (err, videos_list) {
- if (err) return next(err)
-
- res.json(videos_list)
- })
-}
-
-// ---------------------------------------------------------------------------
-
-// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
-function removeTorrent (magnetUri, callback) {
- try {
- webtorrent.remove(magnetUri, callback)
- } catch (err) {
- logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
- return callback(null)
- }
-}
+++ /dev/null
-'use strict'
-
-var constants = require('../initializers/constants')
-
-var apiController = require('./api/' + constants.API_VERSION)
-var viewsController = require('./views')
-
-module.exports = {
- api: apiController,
- views: viewsController
-}
+++ /dev/null
-'use strict'
-
-var express = require('express')
-
-var cacheMiddleware = require('../middlewares').cache
-
-var router = express.Router()
-
-router.get(/^\/(index)?$/, cacheMiddleware.cache(), getIndex)
-router.get('/partials/:directory/:name', cacheMiddleware.cache(), getPartial)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function getIndex (req, res) {
- res.render('index')
-}
-
-function getPartial (req, res) {
- var directory = req.params.directory
- var name = req.params.name
-
- res.render('partials/' + directory + '/' + name)
-}
+++ /dev/null
-'use strict'
-
-var validator = require('validator')
-
-var customValidators = {
- eachIsRemoteVideosAddValid: eachIsRemoteVideosAddValid,
- eachIsRemoteVideosRemoveValid: eachIsRemoteVideosRemoveValid,
- isArray: isArray
-}
-
-function eachIsRemoteVideosAddValid (values) {
- return values.every(function (val) {
- return validator.isLength(val.name, 1, 50) &&
- validator.isLength(val.description, 1, 50) &&
- validator.isLength(val.magnetUri, 10) &&
- validator.isURL(val.podUrl)
- })
-}
-
-function eachIsRemoteVideosRemoveValid (values) {
- return values.every(function (val) {
- return validator.isLength(val.magnetUri, 10)
- })
-}
-
-function isArray (value) {
- return Array.isArray(value)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = customValidators
+++ /dev/null
-// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
-'use strict'
-
-var config = require('config')
-var path = require('path')
-var winston = require('winston')
-winston.emitErrs = true
-
-var logDir = path.join(__dirname, '..', config.get('storage.logs'))
-var logger = new winston.Logger({
- transports: [
- new winston.transports.File({
- level: 'debug',
- filename: path.join(logDir, 'all-logs.log'),
- handleExceptions: true,
- json: true,
- maxsize: 5242880,
- maxFiles: 5,
- colorize: false
- }),
- new winston.transports.Console({
- level: 'debug',
- handleExceptions: true,
- humanReadableUnhandledException: true,
- json: false,
- colorize: true
- })
- ],
- exitOnError: true
-})
-
-logger.stream = {
- write: function (message, encoding) {
- logger.info(message)
- }
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = logger
+++ /dev/null
-'use strict'
-
-var config = require('config')
-var crypto = require('crypto')
-var fs = require('fs')
-var openssl = require('openssl-wrapper')
-var path = require('path')
-var ursa = require('ursa')
-
-var logger = require('./logger')
-
-var certDir = path.join(__dirname, '..', config.get('storage.certs'))
-var algorithm = 'aes-256-ctr'
-
-var peertubeCrypto = {
- checkSignature: checkSignature,
- createCertsIfNotExist: createCertsIfNotExist,
- decrypt: decrypt,
- encrypt: encrypt,
- getCertDir: getCertDir,
- sign: sign
-}
-
-function checkSignature (public_key, raw_data, hex_signature) {
- var crt = ursa.createPublicKey(public_key)
- var is_valid = crt.hashAndVerify('sha256', new Buffer(raw_data).toString('hex'), hex_signature, 'hex')
- return is_valid
-}
-
-function createCertsIfNotExist (callback) {
- certsExist(function (exist) {
- if (exist === true) {
- return callback(null)
- }
-
- createCerts(function (err) {
- return callback(err)
- })
- })
-}
-
-function decrypt (key, data, callback) {
- fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) {
- if (err) return callback(err)
-
- var my_private_key = ursa.createPrivateKey(file)
- var decrypted_key = my_private_key.decrypt(key, 'hex', 'utf8')
- var decrypted_data = symetricDecrypt(data, decrypted_key)
-
- return callback(null, decrypted_data)
- })
-}
-
-function encrypt (public_key, data, callback) {
- var crt = ursa.createPublicKey(public_key)
-
- symetricEncrypt(data, function (err, dataEncrypted) {
- if (err) return callback(err)
-
- var key = crt.encrypt(dataEncrypted.password, 'utf8', 'hex')
- var encrypted = {
- data: dataEncrypted.crypted,
- key: key
- }
-
- callback(null, encrypted)
- })
-}
-
-function getCertDir () {
- return certDir
-}
-
-function sign (data) {
- var myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem'))
- var signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex')
-
- return signature
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = peertubeCrypto
-
-// ---------------------------------------------------------------------------
-
-function certsExist (callback) {
- fs.exists(certDir + 'peertube.key.pem', function (exists) {
- return callback(exists)
- })
-}
-
-function createCerts (callback) {
- certsExist(function (exist) {
- if (exist === true) {
- var string = 'Certs already exist.'
- logger.warning(string)
- return callback(new Error(string))
- }
-
- logger.info('Generating a RSA key...')
- openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) {
- if (err) {
- logger.error('Cannot create private key on this pod.')
- return callback(err)
- }
- logger.info('RSA key generated.')
-
- logger.info('Manage public key...')
- openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) {
- if (err) {
- logger.error('Cannot create public key on this pod.')
- return callback(err)
- }
-
- logger.info('Public key managed.')
- return callback(null)
- })
- })
- })
-}
-
-function generatePassword (callback) {
- crypto.randomBytes(32, function (err, buf) {
- if (err) return callback(err)
-
- callback(null, buf.toString('utf8'))
- })
-}
-
-function symetricDecrypt (text, password) {
- var decipher = crypto.createDecipher(algorithm, password)
- var dec = decipher.update(text, 'hex', 'utf8')
- dec += decipher.final('utf8')
- return dec
-}
-
-function symetricEncrypt (text, callback) {
- generatePassword(function (err, password) {
- if (err) return callback(err)
-
- var cipher = crypto.createCipher(algorithm, password)
- var crypted = cipher.update(text, 'utf8', 'hex')
- crypted += cipher.final('hex')
- callback(null, { crypted: crypted, password: password })
- })
-}
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var request = require('request')
-var replay = require('request-replay')
-
-var constants = require('../initializers/constants')
-var logger = require('./logger')
-var peertubeCrypto = require('./peertubeCrypto')
-
-var http = config.get('webserver.https') ? 'https' : 'http'
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-
-var requests = {
- makeMultipleRetryRequest: makeMultipleRetryRequest
-}
-
-function makeMultipleRetryRequest (all_data, pods, callbackEach, callback) {
- if (!callback) {
- callback = callbackEach
- callbackEach = null
- }
-
- var url = http + '://' + host + ':' + port
- var signature
-
- // Add signature if it is specified in the params
- if (all_data.method === 'POST' && all_data.data && all_data.sign === true) {
- signature = peertubeCrypto.sign(url)
- }
-
- // Make a request for each pod
- async.each(pods, function (pod, callback_each_async) {
- function callbackEachRetryRequest (err, response, body, url, pod) {
- if (callbackEach !== null) {
- callbackEach(err, response, body, url, pod, function () {
- callback_each_async()
- })
- } else {
- callback_each_async()
- }
- }
-
- var params = {
- url: pod.url + all_data.path,
- method: all_data.method
- }
-
- // Add data with POST requst ?
- if (all_data.method === 'POST' && all_data.data) {
- // Encrypt data ?
- if (all_data.encrypt === true) {
- // TODO: ES6 with let
- ;(function (copy_params, copy_url, copy_pod, copy_signature) {
- peertubeCrypto.encrypt(pod.publicKey, JSON.stringify(all_data.data), function (err, encrypted) {
- if (err) return callback(err)
-
- copy_params.json = {
- data: encrypted.data,
- key: encrypted.key
- }
-
- makeRetryRequest(copy_params, copy_url, copy_pod, copy_signature, callbackEachRetryRequest)
- })
- })(params, url, pod, signature)
- } else {
- params.json = { data: all_data.data }
- makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
- }
- } else {
- makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
- }
- }, callback)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = requests
-
-// ---------------------------------------------------------------------------
-
-function makeRetryRequest (params, from_url, to_pod, signature, callbackEach) {
- // Append the signature
- if (signature) {
- params.json.signature = {
- url: from_url,
- signature: signature
- }
- }
-
- logger.debug('Make retry requests to %s.', to_pod.url)
-
- replay(
- request.post(params, function (err, response, body) {
- callbackEach(err, response, body, params.url, to_pod)
- }),
- {
- retries: constants.REQUEST_RETRIES,
- factor: 3,
- maxTimeout: Infinity,
- errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
- }
- ).on('replay', function (replay) {
- logger.info('Replaying request to %s. Request failed: %d %s. Replay number: #%d. Will retry in: %d ms.',
- params.url, replay.error.code, replay.error.message, replay.number, replay.delay)
- })
-}
+++ /dev/null
-'use strict'
-
-var logger = require('./logger')
-
-var utils = {
- cleanForExit: cleanForExit
-}
-
-function cleanForExit (webtorrent_process) {
- logger.info('Gracefully exiting.')
- process.kill(-webtorrent_process.pid)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = utils
+++ /dev/null
-'use strict'
-
-var config = require('config')
-var mkdirp = require('mkdirp')
-var path = require('path')
-
-var checker = {
- checkConfig: checkConfig,
- createDirectoriesIfNotExist: createDirectoriesIfNotExist
-}
-
-// Check the config files
-function checkConfig () {
- var required = [ 'listen.port',
- 'webserver.https', 'webserver.host', 'webserver.port',
- 'database.host', 'database.port', 'database.suffix',
- 'storage.certs', 'storage.uploads', 'storage.logs',
- 'network.friends' ]
- var miss = []
-
- for (var key of required) {
- if (!config.has(key)) {
- miss.push(key)
- }
- }
-
- return miss
-}
-
-// Create directories for the storage if it doesn't exist
-function createDirectoriesIfNotExist () {
- var storages = config.get('storage')
-
- for (var key of Object.keys(storages)) {
- var dir = storages[key]
- try {
- mkdirp.sync(path.join(__dirname, '..', dir))
- } catch (error) {
- throw new Error('Cannot create ' + path + ':' + error)
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = checker
+++ /dev/null
-'use strict'
-
-// API version of our pod
-var API_VERSION = 'v1'
-
-// Score a pod has when we create it as a friend
-var FRIEND_BASE_SCORE = 100
-
-// Time to wait between requests to the friends
-var INTERVAL = 60000
-
-// Number of points we add/remove from a friend after a successful/bad request
-var PODS_SCORE = {
- MALUS: -10,
- BONUS: 10
-}
-
-// Number of retries we make for the make retry requests (to friends...)
-var REQUEST_RETRIES = 10
-
-// Special constants for a test instance
-if (isTestInstance() === true) {
- FRIEND_BASE_SCORE = 20
- INTERVAL = 10000
- REQUEST_RETRIES = 2
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = {
- API_VERSION: API_VERSION,
- FRIEND_BASE_SCORE: FRIEND_BASE_SCORE,
- INTERVAL: INTERVAL,
- PODS_SCORE: PODS_SCORE,
- REQUEST_RETRIES: REQUEST_RETRIES
-}
-
-// ---------------------------------------------------------------------------
-
-function isTestInstance () {
- return (process.env.NODE_ENV === 'test')
-}
+++ /dev/null
-'use strict'
-
-var config = require('config')
-var mongoose = require('mongoose')
-
-var logger = require('../helpers/logger')
-
-var dbname = 'peertube' + config.get('database.suffix')
-var host = config.get('database.host')
-var port = config.get('database.port')
-
-var database = {
- connect: connect
-}
-
-function connect () {
- mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname)
- mongoose.connection.on('error', function () {
- throw new Error('Mongodb connection error.')
- })
-
- mongoose.connection.on('open', function () {
- logger.info('Connected to mongodb.')
- })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = database
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var fs = require('fs')
-var request = require('request')
-
-var constants = require('../initializers/constants')
-var logger = require('../helpers/logger')
-var peertubeCrypto = require('../helpers/peertubeCrypto')
-var Pods = require('../models/pods')
-var poolRequests = require('../lib/poolRequests')
-var requests = require('../helpers/requests')
-var Videos = require('../models/videos')
-
-var http = config.get('webserver.https') ? 'https' : 'http'
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-
-var pods = {
- addVideoToFriends: addVideoToFriends,
- hasFriends: hasFriends,
- makeFriends: makeFriends,
- quitFriends: quitFriends,
- removeVideoToFriends: removeVideoToFriends
-}
-
-function addVideoToFriends (video) {
- // To avoid duplicates
- var id = video.name + video.magnetUri
- // ensure namePath is null
- video.namePath = null
- poolRequests.addRequest(id, 'add', video)
-}
-
-function hasFriends (callback) {
- Pods.count(function (err, count) {
- if (err) return callback(err)
-
- var has_friends = (count !== 0)
- callback(null, has_friends)
- })
-}
-
-function makeFriends (callback) {
- var pods_score = {}
-
- logger.info('Make friends!')
- fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
- if (err) {
- logger.error('Cannot read public cert.')
- return callback(err)
- }
-
- var urls = config.get('network.friends')
-
- async.each(urls, function (url, callback) {
- computeForeignPodsList(url, pods_score, callback)
- }, function (err) {
- if (err) return callback(err)
-
- logger.debug('Pods scores computed.', { pods_score: pods_score })
- var pods_list = computeWinningPods(urls, pods_score)
- logger.debug('Pods that we keep computed.', { pods_to_keep: pods_list })
-
- makeRequestsToWinningPods(cert, pods_list, callback)
- })
- })
-}
-
-function quitFriends (callback) {
- // Stop pool requests
- poolRequests.deactivate()
- // Flush pool requests
- poolRequests.forceSend()
-
- Pods.list(function (err, pods) {
- if (err) return callback(err)
-
- var request = {
- method: 'POST',
- path: '/api/' + constants.API_VERSION + '/pods/remove',
- sign: true,
- encrypt: true,
- data: {
- url: 'me' // Fake data
- }
- }
-
- // Announce we quit them
- requests.makeMultipleRetryRequest(request, pods, function () {
- Pods.removeAll(function (err) {
- poolRequests.activate()
-
- if (err) return callback(err)
-
- logger.info('Broke friends, so sad :(')
-
- Videos.removeAllRemotes(function (err) {
- if (err) return callback(err)
-
- logger.info('Removed all remote videos.')
- callback(null)
- })
- })
- })
- })
-}
-
-function removeVideoToFriends (video) {
- // To avoid duplicates
- var id = video.name + video.magnetUri
- poolRequests.addRequest(id, 'remove', video)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = pods
-
-// ---------------------------------------------------------------------------
-
-function computeForeignPodsList (url, pods_score, callback) {
- // Let's give 1 point to the pod we ask the friends list
- pods_score[url] = 1
-
- getForeignPodsList(url, function (err, foreign_pods_list) {
- if (err) return callback(err)
- if (foreign_pods_list.length === 0) return callback()
-
- async.each(foreign_pods_list, function (foreign_pod, callback_each) {
- var foreign_url = foreign_pod.url
-
- if (pods_score[foreign_url]) pods_score[foreign_url]++
- else pods_score[foreign_url] = 1
-
- callback_each()
- }, function () {
- callback()
- })
- })
-}
-
-function computeWinningPods (urls, pods_score) {
- // Build the list of pods to add
- // Only add a pod if it exists in more than a half base pods
- var pods_list = []
- var base_score = urls.length / 2
- Object.keys(pods_score).forEach(function (pod) {
- if (pods_score[pod] > base_score) pods_list.push({ url: pod })
- })
-
- return pods_list
-}
-
-function getForeignPodsList (url, callback) {
- var path = '/api/' + constants.API_VERSION + '/pods'
-
- request.get(url + path, function (err, response, body) {
- if (err) return callback(err)
-
- callback(null, JSON.parse(body))
- })
-}
-
-function makeRequestsToWinningPods (cert, pods_list, callback) {
- // Stop pool requests
- poolRequests.deactivate()
- // Flush pool requests
- poolRequests.forceSend()
-
- // Get the list of our videos to send to our new friends
- Videos.listOwned(function (err, videos_list) {
- if (err) {
- logger.error('Cannot get the list of videos we own.')
- return callback(err)
- }
-
- var data = {
- url: http + '://' + host + ':' + port,
- publicKey: cert,
- videos: videos_list
- }
-
- requests.makeMultipleRetryRequest(
- { method: 'POST', path: '/api/' + constants.API_VERSION + '/pods/', data: data },
-
- pods_list,
-
- function eachRequest (err, response, body, url, pod, callback_each_request) {
- // We add the pod if it responded correctly with its public certificate
- if (!err && response.statusCode === 200) {
- Pods.add({ url: pod.url, publicKey: body.cert, score: constants.FRIEND_BASE_SCORE }, function (err) {
- if (err) {
- logger.error('Error with adding %s pod.', pod.url, { error: err })
- return callback_each_request()
- }
-
- Videos.addRemotes(body.videos, function (err) {
- if (err) {
- logger.error('Error with adding videos of pod.', pod.url, { error: err })
- return callback_each_request()
- }
-
- logger.debug('Adding remote videos from %s.', pod.url, { videos: body.videos })
- return callback_each_request()
- })
- })
- } else {
- logger.error('Error with adding %s pod.', pod.url, { error: err || new Error('Status not 200') })
- return callback_each_request()
- }
- },
-
- function endRequests (err) {
- // Now we made new friends, we can re activate the pool of requests
- poolRequests.activate()
-
- if (err) {
- logger.error('There was some errors when we wanted to make friends.')
- return callback(err)
- }
-
- logger.debug('makeRequestsToWinningPods finished.')
- return callback(null)
- }
- )
- })
-}
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var pluck = require('lodash-node/compat/collection/pluck')
-
-var constants = require('../initializers/constants')
-var logger = require('../helpers/logger')
-var Pods = require('../models/pods')
-var PoolRequests = require('../models/poolRequests')
-var requests = require('../helpers/requests')
-var Videos = require('../models/videos')
-
-var timer = null
-
-var poolRequests = {
- activate: activate,
- addRequest: addRequest,
- deactivate: deactivate,
- forceSend: forceSend
-}
-
-function activate () {
- logger.info('Pool requests activated.')
- timer = setInterval(makePoolRequests, constants.INTERVAL)
-}
-
-function addRequest (id, type, request) {
- logger.debug('Add request to the pool requests.', { id: id, type: type, request: request })
-
- PoolRequests.findById(id, function (err, entity) {
- if (err) {
- logger.error('Cannot find one pool request.', { error: err })
- return // Abort
- }
-
- if (entity) {
- if (entity.type === type) {
- logger.error('Cannot insert two same requests.')
- return // Abort
- }
-
- // Remove the request of the other type
- PoolRequests.removeRequestById(id, function (err) {
- if (err) {
- logger.error('Cannot remove a pool request.', { error: err })
- return // Abort
- }
- })
- } else {
- PoolRequests.create(id, type, request, function (err) {
- if (err) logger.error('Cannot create a pool request.', { error: err })
- return // Abort
- })
- }
- })
-}
-
-function deactivate () {
- logger.info('Pool requests deactivated.')
- clearInterval(timer)
-}
-
-function forceSend () {
- logger.info('Force pool requests sending.')
- makePoolRequests()
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = poolRequests
-
-// ---------------------------------------------------------------------------
-
-function makePoolRequest (type, requests_to_make, callback) {
- if (!callback) callback = function () {}
-
- Pods.list(function (err, pods) {
- if (err) return callback(err)
-
- var params = {
- encrypt: true,
- sign: true,
- method: 'POST',
- path: null,
- data: requests_to_make
- }
-
- if (type === 'add') {
- params.path = '/api/' + constants.API_VERSION + '/remotevideos/add'
- } else if (type === 'remove') {
- params.path = '/api/' + constants.API_VERSION + '/remotevideos/remove'
- } else {
- return callback(new Error('Unkown pool request type.'))
- }
-
- var bad_pods = []
- var good_pods = []
-
- requests.makeMultipleRetryRequest(params, pods, callbackEachPodFinished, callbackAllPodsFinished)
-
- function callbackEachPodFinished (err, response, body, url, pod, callback_each_pod_finished) {
- if (err || (response.statusCode !== 200 && response.statusCode !== 204)) {
- bad_pods.push(pod._id)
- logger.error('Error sending secure request to %s pod.', url, { error: err || new Error('Status code not 20x') })
- } else {
- good_pods.push(pod._id)
- }
-
- return callback_each_pod_finished()
- }
-
- function callbackAllPodsFinished (err) {
- if (err) return callback(err)
-
- updatePodsScore(good_pods, bad_pods)
- callback(null)
- }
- })
-}
-
-function makePoolRequests () {
- logger.info('Making pool requests to friends.')
-
- PoolRequests.list(function (err, pool_requests) {
- if (err) {
- logger.error('Cannot get the list of pool requests.', { err: err })
- return // Abort
- }
-
- if (pool_requests.length === 0) return
-
- var requests_to_make = {
- add: {
- ids: [],
- requests: []
- },
- remove: {
- ids: [],
- requests: []
- }
- }
-
- async.each(pool_requests, function (pool_request, callback_each) {
- if (pool_request.type === 'add') {
- requests_to_make.add.requests.push(pool_request.request)
- requests_to_make.add.ids.push(pool_request._id)
- } else if (pool_request.type === 'remove') {
- requests_to_make.remove.requests.push(pool_request.request)
- requests_to_make.remove.ids.push(pool_request._id)
- } else {
- logger.error('Unkown pool request type.', { request_type: pool_request.type })
- return // abort
- }
-
- callback_each()
- }, function () {
- // Send the add requests
- if (requests_to_make.add.requests.length !== 0) {
- makePoolRequest('add', requests_to_make.add.requests, function (err) {
- if (err) logger.error('Errors when sent add pool requests.', { error: err })
-
- PoolRequests.removeRequests(requests_to_make.add.ids)
- })
- }
-
- // Send the remove requests
- if (requests_to_make.remove.requests.length !== 0) {
- makePoolRequest('remove', requests_to_make.remove.requests, function (err) {
- if (err) logger.error('Errors when sent remove pool requests.', { error: err })
-
- PoolRequests.removeRequests(requests_to_make.remove.ids)
- })
- }
- })
- })
-}
-
-function removeBadPods () {
- Pods.findBadPods(function (err, pods) {
- if (err) {
- logger.error('Cannot find bad pods.', { error: err })
- return // abort
- }
-
- if (pods.length === 0) return
-
- var urls = pluck(pods, 'url')
- var ids = pluck(pods, '_id')
-
- Videos.removeAllRemotesOf(urls, function (err, r) {
- if (err) {
- logger.error('Cannot remove videos from a pod that we removing.', { error: err })
- } else {
- var videos_removed = r.result.n
- logger.info('Removed %d videos.', videos_removed)
- }
-
- Pods.removeAllByIds(ids, function (err, r) {
- if (err) {
- logger.error('Cannot remove bad pods.', { error: err })
- } else {
- var pods_removed = r.result.n
- logger.info('Removed %d pods.', pods_removed)
- }
- })
- })
- })
-}
-
-function updatePodsScore (good_pods, bad_pods) {
- logger.info('Updating %d good pods and %d bad pods scores.', good_pods.length, bad_pods.length)
-
- Pods.incrementScores(good_pods, constants.PODS_SCORE.BONUS, function (err) {
- if (err) logger.error('Cannot increment scores of good pods.')
- })
-
- Pods.incrementScores(bad_pods, constants.PODS_SCORE.MALUS, function (err) {
- if (err) logger.error('Cannot increment scores of bad pods.')
- removeBadPods()
- })
-}
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var path = require('path')
-var webtorrent = require('../lib/webtorrent')
-
-var logger = require('../helpers/logger')
-var Videos = require('../models/videos')
-
-var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
-
-var videos = {
- seed: seed,
- seedAllExisting: seedAllExisting
-}
-
-function seed (path, callback) {
- logger.info('Seeding %s...', path)
-
- webtorrent.seed(path, function (torrent) {
- logger.info('%s seeded (%s).', path, torrent.magnetURI)
-
- return callback(null, torrent)
- })
-}
-
-function seedAllExisting (callback) {
- Videos.listOwned(function (err, videos_list) {
- if (err) {
- logger.error('Cannot get list of the videos to seed.')
- return callback(err)
- }
-
- async.each(videos_list, function (video, each_callback) {
- seed(uploadDir + video.namePath, function (err) {
- if (err) {
- logger.error('Cannot seed this video.')
- return callback(err)
- }
-
- each_callback(null)
- })
- }, callback)
- })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = videos
+++ /dev/null
-'use strict'
-
-var config = require('config')
-var ipc = require('node-ipc')
-var pathUtils = require('path')
-var spawn = require('electron-spawn')
-
-var logger = require('../helpers/logger')
-
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-var nodeKey = 'webtorrentnode' + port
-var processKey = 'webtorrentprocess' + port
-ipc.config.silent = true
-ipc.config.id = nodeKey
-
-var webtorrent = {
- add: add,
- app: null, // Pid of the app
- create: create,
- remove: remove,
- seed: seed,
- silent: false // Useful for beautiful tests
-}
-
-function create (options, callback) {
- if (typeof options === 'function') {
- callback = options
- options = {}
- }
-
- // Override options
- if (options.host) host = options.host
- if (options.port) {
- port = options.port
- nodeKey = 'webtorrentnode' + port
- processKey = 'webtorrentprocess' + port
- ipc.config.id = nodeKey
- }
-
- ipc.serve(function () {
- if (!webtorrent.silent) logger.info('IPC server ready.')
-
- // Run a timeout of 30s after which we exit the process
- var timeout_webtorrent_process = setTimeout(function () {
- throw new Error('Timeout : cannot run the webtorrent process. Please ensure you have electron-prebuilt npm package installed with xvfb-run.')
- }, 30000)
-
- ipc.server.on(processKey + '.ready', function () {
- if (!webtorrent.silent) logger.info('Webtorrent process ready.')
- clearTimeout(timeout_webtorrent_process)
- callback()
- })
-
- ipc.server.on(processKey + '.exception', function (data) {
- throw new Error('Received exception error from webtorrent process.' + data.exception)
- })
-
- var webtorrent_process = spawn(pathUtils.join(__dirname, 'webtorrentProcess.js'), host, port, { detached: true })
- webtorrent_process.stderr.on('data', function (data) {
- // logger.debug('Webtorrent process stderr: ', data.toString())
- })
-
- webtorrent_process.stdout.on('data', function (data) {
- // logger.debug('Webtorrent process:', data.toString())
- })
-
- webtorrent.app = webtorrent_process
- })
-
- ipc.server.start()
-}
-
-function seed (path, callback) {
- var extension = pathUtils.extname(path)
- var basename = pathUtils.basename(path, extension)
- var data = {
- _id: basename,
- args: {
- path: path
- }
- }
-
- if (!webtorrent.silent) logger.debug('Node wants to seed %s.', data._id)
-
- // Finish signal
- var event_key = nodeKey + '.seedDone.' + data._id
- ipc.server.on(event_key, function listener (received) {
- if (!webtorrent.silent) logger.debug('Process seeded torrent %s.', received.magnetUri)
-
- // This is a fake object, we just use the magnetUri in this project
- var torrent = {
- magnetURI: received.magnetUri
- }
-
- ipc.server.off(event_key)
- callback(torrent)
- })
-
- ipc.server.broadcast(processKey + '.seed', data)
-}
-
-function add (magnetUri, callback) {
- var data = {
- _id: magnetUri,
- args: {
- magnetUri: magnetUri
- }
- }
-
- if (!webtorrent.silent) logger.debug('Node wants to add ' + data._id)
-
- // Finish signal
- var event_key = nodeKey + '.addDone.' + data._id
- ipc.server.on(event_key, function (received) {
- if (!webtorrent.silent) logger.debug('Process added torrent.')
-
- // This is a fake object, we just use the magnetUri in this project
- var torrent = {
- files: received.files
- }
-
- ipc.server.off(event_key)
- callback(torrent)
- })
-
- ipc.server.broadcast(processKey + '.add', data)
-}
-
-function remove (magnetUri, callback) {
- var data = {
- _id: magnetUri,
- args: {
- magnetUri: magnetUri
- }
- }
-
- if (!webtorrent.silent) logger.debug('Node wants to stop seeding %s.', data._id)
-
- // Finish signal
- var event_key = nodeKey + '.removeDone.' + data._id
- ipc.server.on(event_key, function (received) {
- if (!webtorrent.silent) logger.debug('Process removed torrent %s.', data._id)
-
- var err = null
- if (received.err) err = received.err
-
- ipc.server.off(event_key)
- callback(err)
- })
-
- ipc.server.broadcast(processKey + '.remove', data)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = webtorrent
+++ /dev/null
-'use strict'
-
-var WebTorrent = require('webtorrent')
-var ipc = require('node-ipc')
-
-function webtorrent (args) {
- if (args.length !== 3) {
- throw new Error('Wrong arguments number: ' + args.length + '/3')
- }
-
- var host = args[1]
- var port = args[2]
- var nodeKey = 'webtorrentnode' + port
- var processKey = 'webtorrentprocess' + port
-
- ipc.config.silent = true
- ipc.config.id = processKey
-
- if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = []
- else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket'
- var wt = new WebTorrent({ dht: false })
-
- function seed (data) {
- var args = data.args
- var path = args.path
- var _id = data._id
-
- wt.seed(path, { announceList: '' }, function (torrent) {
- var to_send = {
- magnetUri: torrent.magnetURI
- }
-
- ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send)
- })
- }
-
- function add (data) {
- var args = data.args
- var magnetUri = args.magnetUri
- var _id = data._id
-
- wt.add(magnetUri, function (torrent) {
- var to_send = {
- files: []
- }
-
- torrent.files.forEach(function (file) {
- to_send.files.push({ path: file.path })
- })
-
- ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send)
- })
- }
-
- function remove (data) {
- var args = data.args
- var magnetUri = args.magnetUri
- var _id = data._id
-
- try {
- wt.remove(magnetUri, callback)
- } catch (err) {
- console.log('Cannot remove the torrent from WebTorrent.')
- return callback(null)
- }
-
- function callback () {
- var to_send = {}
- ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send)
- }
- }
-
- console.log('Configuration: ' + host + ':' + port)
- console.log('Connecting to IPC...')
-
- ipc.connectTo(nodeKey, function () {
- ipc.of[nodeKey].on(processKey + '.seed', seed)
- ipc.of[nodeKey].on(processKey + '.add', add)
- ipc.of[nodeKey].on(processKey + '.remove', remove)
-
- ipc.of[nodeKey].emit(processKey + '.ready')
- console.log('Ready.')
- })
-
- process.on('uncaughtException', function (e) {
- ipc.of[nodeKey].emit(processKey + '.exception', { exception: e })
- })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = webtorrent
+++ /dev/null
-'use strict'
-
-var cacheMiddleware = {
- cache: cache
-}
-
-function cache (cache) {
- return function (req, res, next) {
- // If we want explicitly a cache
- // Or if we don't specify if we want a cache or no and we are in production
- if (cache === true || (cache !== false && process.env.NODE_ENV === 'production')) {
- res.setHeader('Cache-Control', 'public')
- } else {
- res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
- }
-
- next()
- }
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = cacheMiddleware
+++ /dev/null
-'use strict'
-
-var cacheMiddleware = require('./cache')
-var reqValidatorsMiddleware = require('./reqValidators')
-var secureMiddleware = require('./secure')
-
-var middlewares = {
- cache: cacheMiddleware,
- reqValidators: reqValidatorsMiddleware,
- secure: secureMiddleware
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = middlewares
+++ /dev/null
-'use strict'
-
-var podsReqValidators = require('./pods')
-var remoteReqValidators = require('./remote')
-var videosReqValidators = require('./videos')
-
-var reqValidators = {
- pods: podsReqValidators,
- remote: remoteReqValidators,
- videos: videosReqValidators
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidators
+++ /dev/null
-'use strict'
-
-var checkErrors = require('./utils').checkErrors
-var friends = require('../../lib/friends')
-var logger = require('../../helpers/logger')
-
-var reqValidatorsPod = {
- makeFriends: makeFriends,
- podsAdd: podsAdd
-}
-
-function makeFriends (req, res, next) {
- friends.hasFriends(function (err, has_friends) {
- if (err) {
- logger.error('Cannot know if we have friends.', { error: err })
- res.sendStatus(500)
- }
-
- if (has_friends === true) {
- // We need to quit our friends before make new ones
- res.sendStatus(409)
- } else {
- return next()
- }
- })
-}
-
-function podsAdd (req, res, next) {
- req.checkBody('data.url', 'Should have an url').notEmpty().isURL({ require_protocol: true })
- req.checkBody('data.publicKey', 'Should have a public key').notEmpty()
-
- logger.debug('Checking podsAdd parameters', { parameters: req.body })
-
- checkErrors(req, res, next)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsPod
+++ /dev/null
-'use strict'
-
-var checkErrors = require('./utils').checkErrors
-var logger = require('../../helpers/logger')
-
-var reqValidatorsRemote = {
- remoteVideosAdd: remoteVideosAdd,
- remoteVideosRemove: remoteVideosRemove,
- secureRequest: secureRequest
-}
-
-function remoteVideosAdd (req, res, next) {
- req.checkBody('data').isArray()
- req.checkBody('data').eachIsRemoteVideosAddValid()
-
- logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body })
-
- checkErrors(req, res, next)
-}
-
-function remoteVideosRemove (req, res, next) {
- req.checkBody('data').isArray()
- req.checkBody('data').eachIsRemoteVideosRemoveValid()
-
- logger.debug('Checking remoteVideosRemove parameters', { parameters: req.body })
-
- checkErrors(req, res, next)
-}
-
-function secureRequest (req, res, next) {
- req.checkBody('signature.url', 'Should have a signature url').isURL()
- req.checkBody('signature.signature', 'Should have a signature').notEmpty()
- req.checkBody('key', 'Should have a key').notEmpty()
- req.checkBody('data', 'Should have data').notEmpty()
-
- logger.debug('Checking secureRequest parameters', { parameters: { data: req.body.data, keyLength: req.body.key.length } })
-
- checkErrors(req, res, next)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsRemote
+++ /dev/null
-'use strict'
-
-var util = require('util')
-
-var logger = require('../../helpers/logger')
-
-var reqValidatorsUtils = {
- checkErrors: checkErrors
-}
-
-function checkErrors (req, res, next, status_code) {
- if (status_code === undefined) status_code = 400
- var errors = req.validationErrors()
-
- if (errors) {
- logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors })
- return res.status(status_code).send('There have been validation errors: ' + util.inspect(errors))
- }
-
- return next()
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsUtils
+++ /dev/null
-'use strict'
-
-var checkErrors = require('./utils').checkErrors
-var logger = require('../../helpers/logger')
-var Videos = require('../../models/videos')
-
-var reqValidatorsVideos = {
- videosAdd: videosAdd,
- videosGet: videosGet,
- videosRemove: videosRemove,
- videosSearch: videosSearch
-}
-
-function videosAdd (req, res, next) {
- req.checkFiles('input_video[0].originalname', 'Should have an input video').notEmpty()
- req.checkFiles('input_video[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i)
- req.checkBody('name', 'Should have a name').isLength(1, 50)
- req.checkBody('description', 'Should have a description').isLength(1, 250)
-
- logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
-
- checkErrors(req, res, next)
-}
-
-function videosGet (req, res, next) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
-
- logger.debug('Checking videosGet parameters', { parameters: req.params })
-
- checkErrors(req, res, function () {
- Videos.getVideoState(req.params.id, function (err, state) {
- if (err) {
- logger.error('Error in videosGet request validator.', { error: err })
- res.sendStatus(500)
- }
-
- if (state.exist === false) return res.status(404).send('Video not found')
-
- next()
- })
- })
-}
-
-function videosRemove (req, res, next) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
-
- logger.debug('Checking videosRemove parameters', { parameters: req.params })
-
- checkErrors(req, res, function () {
- Videos.getVideoState(req.params.id, function (err, state) {
- if (err) {
- logger.error('Error in videosRemove request validator.', { error: err })
- res.sendStatus(500)
- }
-
- if (state.exist === false) return res.status(404).send('Video not found')
- else if (state.owned === false) return res.status(403).send('Cannot remove video of another pod')
-
- next()
- })
- })
-}
-
-function videosSearch (req, res, next) {
- req.checkParams('name', 'Should have a name').notEmpty()
-
- logger.debug('Checking videosSearch parameters', { parameters: req.params })
-
- checkErrors(req, res, next)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsVideos
+++ /dev/null
-'use strict'
-
-var logger = require('../helpers/logger')
-var peertubeCrypto = require('../helpers/peertubeCrypto')
-var Pods = require('../models/pods')
-
-var secureMiddleware = {
- decryptBody: decryptBody
-}
-
-function decryptBody (req, res, next) {
- var url = req.body.signature.url
- Pods.findByUrl(url, function (err, pod) {
- if (err) {
- logger.error('Cannot get signed url in decryptBody.', { error: err })
- return res.sendStatus(500)
- }
-
- if (pod === null) {
- logger.error('Unknown pod %s.', url)
- return res.sendStatus(403)
- }
-
- logger.debug('Decrypting body from %s.', url)
-
- var signature_ok = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature)
-
- if (signature_ok === true) {
- peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) {
- if (err) {
- logger.error('Cannot decrypt data.', { error: err })
- return res.sendStatus(500)
- }
-
- req.body.data = JSON.parse(decrypted)
- delete req.body.key
-
- next()
- })
- } else {
- logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url)
- return res.sendStatus(403)
- }
- })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = secureMiddleware
+++ /dev/null
-'use strict'
-
-var mongoose = require('mongoose')
-
-var constants = require('../initializers/constants')
-var logger = require('../helpers/logger')
-
-// ---------------------------------------------------------------------------
-
-var podsSchema = mongoose.Schema({
- url: String,
- publicKey: String,
- score: { type: Number, max: constants.FRIEND_BASE_SCORE }
-})
-var PodsDB = mongoose.model('pods', podsSchema)
-
-// ---------------------------------------------------------------------------
-
-var Pods = {
- add: add,
- count: count,
- findByUrl: findByUrl,
- findBadPods: findBadPods,
- incrementScores: incrementScores,
- list: list,
- remove: remove,
- removeAll: removeAll,
- removeAllByIds: removeAllByIds
-}
-
-// TODO: check if the pod is not already a friend
-function add (data, callback) {
- if (!callback) callback = function () {}
- var params = {
- url: data.url,
- publicKey: data.publicKey,
- score: constants.FRIEND_BASE_SCORE
- }
-
- PodsDB.create(params, callback)
-}
-
-function count (callback) {
- return PodsDB.count(callback)
-}
-
-function findBadPods (callback) {
- PodsDB.find({ score: 0 }, callback)
-}
-
-function findByUrl (url, callback) {
- PodsDB.findOne({ url: url }, callback)
-}
-
-function incrementScores (ids, value, callback) {
- if (!callback) callback = function () {}
- PodsDB.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback)
-}
-
-function list (callback) {
- PodsDB.find(function (err, pods_list) {
- if (err) {
- logger.error('Cannot get the list of the pods.')
- return callback(err)
- }
-
- return callback(null, pods_list)
- })
-}
-
-function remove (url, callback) {
- if (!callback) callback = function () {}
- PodsDB.remove({ url: url }, callback)
-}
-
-function removeAll (callback) {
- if (!callback) callback = function () {}
- PodsDB.remove(callback)
-}
-
-function removeAllByIds (ids, callback) {
- if (!callback) callback = function () {}
- PodsDB.remove({ _id: { $in: ids } }, callback)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = Pods
+++ /dev/null
-'use strict'
-
-var mongoose = require('mongoose')
-
-var logger = require('../helpers/logger')
-
-// ---------------------------------------------------------------------------
-
-var poolRequestsSchema = mongoose.Schema({
- type: String,
- id: String, // Special id to find duplicates (video created we want to remove...)
- request: mongoose.Schema.Types.Mixed
-})
-var PoolRequestsDB = mongoose.model('poolRequests', poolRequestsSchema)
-
-// ---------------------------------------------------------------------------
-
-var PoolRequests = {
- create: create,
- findById: findById,
- list: list,
- removeRequestById: removeRequestById,
- removeRequests: removeRequests
-}
-
-function create (id, type, request, callback) {
- PoolRequestsDB.create({ id: id, type: type, request: request }, callback)
-}
-
-function findById (id, callback) {
- PoolRequestsDB.findOne({ id: id }, callback)
-}
-
-function list (callback) {
- PoolRequestsDB.find({}, { _id: 1, type: 1, request: 1 }, callback)
-}
-
-function removeRequestById (id, callback) {
- PoolRequestsDB.remove({ id: id }, callback)
-}
-
-function removeRequests (ids) {
- PoolRequestsDB.remove({ _id: { $in: ids } }, function (err) {
- if (err) {
- logger.error('Cannot remove requests from the pool requests database.', { error: err })
- return // Abort
- }
-
- logger.info('Pool requests flushed.')
- })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = PoolRequests
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var dz = require('dezalgo')
-var fs = require('fs')
-var mongoose = require('mongoose')
-var path = require('path')
-
-var logger = require('../helpers/logger')
-
-var http = config.get('webserver.https') === true ? 'https' : 'http'
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
-
-// ---------------------------------------------------------------------------
-
-var videosSchema = mongoose.Schema({
- name: String,
- namePath: String,
- description: String,
- magnetUri: String,
- podUrl: String
-})
-var VideosDB = mongoose.model('videos', videosSchema)
-
-// ---------------------------------------------------------------------------
-
-var Videos = {
- add: add,
- addRemotes: addRemotes,
- get: get,
- getVideoState: getVideoState,
- isOwned: isOwned,
- list: list,
- listOwned: listOwned,
- removeOwned: removeOwned,
- removeAllRemotes: removeAllRemotes,
- removeAllRemotesOf: removeAllRemotesOf,
- removeRemotesOfByMagnetUris: removeRemotesOfByMagnetUris,
- search: search
-}
-
-function add (video, callback) {
- logger.info('Adding %s video to database.', video.name)
-
- var params = video
- params.podUrl = http + '://' + host + ':' + port
-
- VideosDB.create(params, function (err, video) {
- if (err) {
- logger.error('Cannot insert this video into database.')
- return callback(err)
- }
-
- callback(null)
- })
-}
-
-// TODO: avoid doublons
-function addRemotes (videos, callback) {
- if (!callback) callback = function () {}
-
- var to_add = []
-
- async.each(videos, function (video, callback_each) {
- callback_each = dz(callback_each)
- logger.debug('Add remote video from pod: %s', video.podUrl)
-
- var params = {
- name: video.name,
- namePath: null,
- description: video.description,
- magnetUri: video.magnetUri,
- podUrl: video.podUrl
- }
-
- to_add.push(params)
-
- callback_each()
- }, function () {
- VideosDB.create(to_add, function (err, videos) {
- if (err) {
- logger.error('Cannot insert this remote video.')
- return callback(err)
- }
-
- return callback(null, videos)
- })
- })
-}
-
-function get (id, callback) {
- VideosDB.findById(id, function (err, video) {
- if (err) {
- logger.error('Cannot get this video.')
- return callback(err)
- }
-
- return callback(null, video)
- })
-}
-
-function getVideoState (id, callback) {
- get(id, function (err, video) {
- if (err) return callback(err)
-
- var exist = (video !== null)
- var owned = false
- if (exist === true) {
- owned = (video.namePath !== null)
- }
-
- return callback(null, { exist: exist, owned: owned })
- })
-}
-
-function isOwned (id, callback) {
- VideosDB.findById(id, function (err, video) {
- if (err || !video) {
- if (!err) err = new Error('Cannot find this video.')
- logger.error('Cannot find this video.')
- return callback(err)
- }
-
- if (video.namePath === null) {
- var error_string = 'Cannot remove the video of another pod.'
- logger.error(error_string)
- return callback(new Error(error_string), false, video)
- }
-
- callback(null, true, video)
- })
-}
-
-function list (callback) {
- VideosDB.find(function (err, videos_list) {
- if (err) {
- logger.error('Cannot get the list of the videos.')
- return callback(err)
- }
-
- return callback(null, videos_list)
- })
-}
-
-function listOwned (callback) {
- // If namePath is not null this is *our* video
- VideosDB.find({ namePath: { $ne: null } }, function (err, videos_list) {
- if (err) {
- logger.error('Cannot get the list of owned videos.')
- return callback(err)
- }
-
- return callback(null, videos_list)
- })
-}
-
-function removeOwned (id, callback) {
- VideosDB.findByIdAndRemove(id, function (err, video) {
- if (err) {
- logger.error('Cannot remove the torrent.')
- return callback(err)
- }
-
- fs.unlink(uploadDir + video.namePath, function (err) {
- if (err) {
- logger.error('Cannot remove this video file.')
- return callback(err)
- }
-
- callback(null)
- })
- })
-}
-
-function removeAllRemotes (callback) {
- VideosDB.remove({ namePath: null }, callback)
-}
-
-function removeAllRemotesOf (fromUrl, callback) {
- VideosDB.remove({ podUrl: fromUrl }, callback)
-}
-
-// Use the magnet Uri because the _id field is not the same on different servers
-function removeRemotesOfByMagnetUris (fromUrl, magnetUris, callback) {
- if (callback === undefined) callback = function () {}
-
- VideosDB.find({ magnetUri: { $in: magnetUris } }, function (err, videos) {
- if (err || !videos) {
- logger.error('Cannot find the torrent URI of these remote videos.')
- return callback(err)
- }
-
- var to_remove = []
- async.each(videos, function (video, callback_async) {
- callback_async = dz(callback_async)
-
- if (video.podUrl !== fromUrl) {
- logger.error('The pod %s has not the rights on the video of %s.', fromUrl, video.podUrl)
- } else {
- to_remove.push(video._id)
- }
-
- callback_async()
- }, function () {
- VideosDB.remove({ _id: { $in: to_remove } }, function (err) {
- if (err) {
- logger.error('Cannot remove the remote videos.')
- return callback(err)
- }
-
- logger.info('Removed remote videos from %s.', fromUrl)
- callback(null)
- })
- })
- })
-}
-
-function search (name, callback) {
- VideosDB.find({ name: new RegExp(name) }, function (err, videos) {
- if (err) {
- logger.error('Cannot search the videos.')
- return callback(err)
- }
-
- return callback(null, videos)
- })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = Videos
+++ /dev/null
-;(function () {
- 'use strict'
-
- var $ = require('jquery')
- require('blueimp-file-upload')
-
- var WebTorrent = require('webtorrent')
- var client = new WebTorrent({ dht: false })
-
- var $content = $('#ajax_load')
-
- // Webtorrent events
- client.on('error', function (err) {
- console.error(err)
- })
-
- client.on('warning', function (err) {
- console.warning(err)
- })
-
- // Events of the panel
- $('#panel_get_videos').on('click', function () {
- getVideos()
- })
-
- $('#panel_upload_video').on('click', function () {
- uploadVideo()
- })
-
- $('#panel_make_friends').on('click', function () {
- makeFriends()
- })
-
- $('#panel_quit_friends').on('click', function () {
- quitFriends()
- })
-
- $('#search-video').on('keyup', function (e) {
- var search = $(this).val()
-
- if (search === '') return
-
- if (e.keyCode === 13) {
- $.ajax({
- url: '/api/v1/videos/search/' + search,
- type: 'GET',
- dataType: 'json',
- success: function (videos) {
- printVideos(videos)
- }
- })
- }
- })
-
- // Join a new network
- function makeFriends () {
- $.ajax({
- url: '/api/v1/pods/makefriends',
- type: 'GET',
- dataType: 'json',
- statusCode: {
- 409: function () {
- alert('Already made friends.')
- }
- },
- success: function () {
- alert('Made friends!')
- }
- })
- }
-
- function quitFriends () {
- $.ajax({
- url: '/api/v1/pods/quitfriends',
- type: 'GET',
- dataType: 'json',
- success: function () {
- alert('Quit friends!')
- }
- })
- }
-
- function printVideos (videos) {
- $content.empty()
-
- if (videos.length === 0) {
- $content.text('There is no videos.')
- }
-
- videos.forEach(function (video) {
- var $video = $('<div></div>').addClass('video')
-
- var $video_name = $('<span></span>').addClass('video_name').text(video.name)
- var $video_pod = $('<span></span>').addClass('video_pod_url').text(video.podUrl)
- var $header = $('<div></div>').append([ $video_name, $video_pod ])
-
- if (video.namePath !== null) {
- var $remove = $('<span></span>').addClass('span_action glyphicon glyphicon-remove')
-
- // Remove the video
- $remove.on('click', function () {
- if (!confirm('Are you sure ?')) return
-
- removeVideo(video)
- })
-
- $header.append($remove)
- }
-
- var $video_description = $('<div></div>').addClass('video_description').text(video.description)
-
- // Get the video
- $video_name.on('click', function () {
- getVideo(video)
- })
-
- if (!video.magnetUri) {
- $remove.css('display', 'none')
- }
-
- $video.append([ $header, $video_description ])
- $content.append($video)
- })
- }
-
- // Upload the video, the server will seed it
- function uploadVideo () {
- // Creating all the elements
- var $video_label = $('<label></label>').attr('for', 'name').text('Video name')
- var $video_name = $('<input></input>').addClass('form-control').attr({
- name: 'name',
- id: 'name'
- })
- var $video_block = $('<div></div>').addClass('form-group').append([ $video_label, $video_name ])
-
- var $title = $('<h3></h3>').text('Upload a video')
-
- var $button_text = $('<span></span>').text('Select the video...')
- var $input_video = $('<input></input>').attr({
- type: 'file',
- name: 'input_video',
- id: 'input_video'
- })
- var $button = $('<div></div>').addClass('btn btn-default btn-file').append([ $button_text, $input_video ])
-
- var $description_label = $('<label></label>').attr('for', 'description').text('Description')
- var $description_text = $('<textarea></textarea>').addClass('form-control').attr({
- name: 'description',
- id: 'description',
- placeholder: 'Description...'
- })
- var $description = $('<div></div>').addClass('form-group').append([ $description_label, $description_text ])
-
- var $bar = $('<progress></progress').attr('value', '0').css('display', 'none')
- var $progress_bar = $('<div><div>').attr('id', 'progress').append($bar)
-
- var $input_submit = $('<input></input>').addClass('btn btn-default').attr({
- type: 'button',
- value: 'Upload'
- })
-
- // JQuery plugin
- var $form_video = $('<form></form>').append([ $video_block, $button, $progress_bar, $description, $input_submit ])
- $form_video.fileupload({
- singleFileUploads: true,
- multipart: true,
- url: '/api/v1/videos',
- autoupload: false,
- add: function (e, data) {
- var $text = $('<span></span>').addClass('name_file').text(data['files'][0]['name'])
- $text.insertAfter($button)
- $input_submit.off('click').on('click', function () {
- $bar.css('display', 'block')
- data.formData = $form_video.serializeArray()
- data.submit()
- })
- },
- progressall: function (e, data) {
- $bar.attr({
- value: data.loaded,
- max: data.total
- })
- },
- done: function (e, data) {
- // Print all the videos once it's finished
- getVideos()
- }
- })
-
- $content.empty()
- $content.append([ $title, $form_video ])
- }
-
- // Print the list of all the videos
- function getVideos () {
- $.ajax({
- url: '/api/v1/videos/',
- dataType: 'json',
- type: 'GET',
- success: function (videos) {
- printVideos(videos)
- }
- })
- }
-
- function removeVideo (video) {
- $.ajax({
- url: '/api/v1/videos/' + video._id,
- type: 'DELETE',
- success: function (response, status) {
- getVideos()
- }
- })
- }
-
- // Get the video: add the torrent file and stream it into a video tag
- function getVideo (video) {
- var $waiting = $('<img></img>').addClass('center-block loading').attr('src', '/images/loading.gif')
- $content.empty()
- $content.append($waiting)
-
- console.log('Getting ' + video)
- client.add(video.magnetUri, function (torrent) {
- var $embed = $('<div></div>').addClass('embed-responsive embed-responsive-16by9')
-
- $content.empty()
- $content.append($embed)
-
- // Got torrent metadata!
- console.log('Torrent info hash:', torrent.infoHash)
-
- // Let's say the first file is a webm (vp8) or mp4 (h264) video...
- var file = torrent.files[0]
-
- file.appendTo($embed.get(0), function (err) {
- if (err) {
- alert('Cannot append the file.')
- console.error(err)
- }
- })
- })
- }
-
- getVideos()
-})()
+++ /dev/null
-$icon-font-path: "/stylesheets/vendor/fonts/bootstrap/";
-
-@import "bootstrap-variables";
-@import "_bootstrap";
-@import "base";
-@import "index";
\ No newline at end of file
+++ /dev/null
-body {
- padding: 20px;
-}
-
-footer {
- border-top: 1px solid rgba(0, 0, 0, 0.2);
- padding-top: 10px;
- text-align: center;
- font-size: small;
-}
-
-.search-group {
- .search-btn {
- position: relative;
- left: -40px;
- top: 0;
-
- &:hover { text-decoration: none; }
- }
-}
\ No newline at end of file
+++ /dev/null
-// Override Bootstrap variables here (defaults from bootstrap-sass v3.3.6):
-
-//
-// Variables
-// --------------------------------------------------
-
-
-//== Colors
-//
-//## Gray and brand colors for use across Bootstrap.
-
-// $gray-base: #000
-// $gray-darker: lighten($gray-base, 13.5%) // #222
-// $gray-dark: lighten($gray-base, 20%) // #333
-// $gray: lighten($gray-base, 33.5%) // #555
-// $gray-light: lighten($gray-base, 46.7%) // #777
-// $gray-lighter: lighten($gray-base, 93.5%) // #eee
-
-// $brand-primary: darken(#428bca, 6.5%) // #337ab7
-// $brand-success: #5cb85c
-// $brand-info: #5bc0de
-// $brand-warning: #f0ad4e
-// $brand-danger: #d9534f
-
-
-//== Scaffolding
-//
-//## Settings for some of the most global styles.
-
-//** Background color for `<body>`.
-// $body-bg: #fff
-//** Global text color on `<body>`.
-// $text-color: $gray-dark
-
-//** Global textual link color.
-// $link-color: $brand-primary
-//** Link hover color set via `darken()` function.
-// $link-hover-color: darken($link-color, 15%)
-//** Link hover decoration.
-// $link-hover-decoration: underline
-
-
-//== Typography
-//
-//## Font, line-height, and color for body text, headings, and more.
-
-// $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif
-// $font-family-serif: Georgia, "Times New Roman", Times, serif
-//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
-// $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace
-// $font-family-base: $font-family-sans-serif
-
-// $font-size-base: 14px
-// $font-size-large: ceil(($font-size-base * 1.25)) // ~18px
-// $font-size-small: ceil(($font-size-base * 0.85)) // ~12px
-
-// $font-size-h1: floor(($font-size-base * 2.6)) // ~36px
-// $font-size-h2: floor(($font-size-base * 2.15)) // ~30px
-// $font-size-h3: ceil(($font-size-base * 1.7)) // ~24px
-// $font-size-h4: ceil(($font-size-base * 1.25)) // ~18px
-// $font-size-h5: $font-size-base
-// $font-size-h6: ceil(($font-size-base * 0.85)) // ~12px
-
-//** Unit-less `line-height` for use in components like buttons.
-// $line-height-base: 1.428571429 // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-// $line-height-computed: floor(($font-size-base * $line-height-base)) // ~20px
-
-//** By default, this inherits from the `<body>`.
-// $headings-font-family: inherit
-// $headings-font-weight: 500
-// $headings-line-height: 1.1
-// $headings-color: inherit
-
-
-//== Iconography
-//
-//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-//** Load fonts from this directory.
-
-// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
-// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
-// $icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/")
-
-//** File name for all font files.
-// $icon-font-name: "glyphicons-halflings-regular"
-//** Element ID within SVG icon file.
-// $icon-font-svg-id: "glyphicons_halflingsregular"
-
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-// $padding-base-vertical: 6px
-// $padding-base-horizontal: 12px
-
-// $padding-large-vertical: 10px
-// $padding-large-horizontal: 16px
-
-// $padding-small-vertical: 5px
-// $padding-small-horizontal: 10px
-
-// $padding-xs-vertical: 1px
-// $padding-xs-horizontal: 5px
-
-// $line-height-large: 1.3333333 // extra decimals for Win 8.1 Chrome
-// $line-height-small: 1.5
-
-$border-radius-base: 0;
-$border-radius-large: 0;
-$border-radius-small: 0;
-
-//** Global color for active items (e.g., navs or dropdowns).
-// $component-active-color: #fff
-//** Global background color for active items (e.g., navs or dropdowns).
-// $component-active-bg: $brand-primary
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-// $caret-width-base: 4px
-//** Carets increase slightly in size for larger components.
-// $caret-width-large: 5px
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for `<th>`s and `<td>`s.
-// $table-cell-padding: 8px
-//** Padding for cells in `.table-condensed`.
-// $table-condensed-cell-padding: 5px
-
-//** Default background color used for all tables.
-// $table-bg: transparent
-//** Background color used for `.table-striped`.
-// $table-bg-accent: #f9f9f9
-//** Background color used for `.table-hover`.
-// $table-bg-hover: #f5f5f5
-// $table-bg-active: $table-bg-hover
-
-//** Border color for table and cell borders.
-// $table-border-color: #ddd
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-// $btn-font-weight: normal
-
-// $btn-default-color: #333
-// $btn-default-bg: #fff
-// $btn-default-border: #ccc
-
-// $btn-primary-color: #fff
-// $btn-primary-bg: $brand-primary
-// $btn-primary-border: darken($btn-primary-bg, 5%)
-
-// $btn-success-color: #fff
-// $btn-success-bg: $brand-success
-// $btn-success-border: darken($btn-success-bg, 5%)
-
-// $btn-info-color: #fff
-// $btn-info-bg: $brand-info
-// $btn-info-border: darken($btn-info-bg, 5%)
-
-// $btn-warning-color: #fff
-// $btn-warning-bg: $brand-warning
-// $btn-warning-border: darken($btn-warning-bg, 5%)
-
-// $btn-danger-color: #fff
-// $btn-danger-bg: $brand-danger
-// $btn-danger-border: darken($btn-danger-bg, 5%)
-
-// $btn-link-disabled-color: $gray-light
-
-// Allows for customizing button radius independently from global border radius
-// $btn-border-radius-base: $border-radius-base
-// $btn-border-radius-large: $border-radius-large
-// $btn-border-radius-small: $border-radius-small
-
-
-//== Forms
-//
-//##
-
-//** `<input>` background color
-// $input-bg: #fff
-//** `<input disabled>` background color
-// $input-bg-disabled: $gray-lighter
-
-//** Text color for `<input>`s
-// $input-color: $gray
-//** `<input>` border color
-// $input-border: #ccc
-
-// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
-//** Default `.form-control` border radius
-// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
-// $input-border-radius: $border-radius-base
-//** Large `.form-control` border radius
-// $input-border-radius-large: $border-radius-large
-//** Small `.form-control` border radius
-// $input-border-radius-small: $border-radius-small
-
-//** Border color for inputs on focus
-// $input-border-focus: #66afe9
-
-//** Placeholder text color
-// $input-color-placeholder: #999
-
-//** Default `.form-control` height
-// $input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2)
-//** Large `.form-control` height
-// $input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2)
-//** Small `.form-control` height
-// $input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2)
-
-//** `.form-group` margin
-// $form-group-margin-bottom: 15px
-
-// $legend-color: $gray-dark
-// $legend-border-color: #e5e5e5
-
-//** Background color for textual input addons
-// $input-group-addon-bg: $gray-lighter
-//** Border color for textual input addons
-// $input-group-addon-border-color: $input-border
-
-//** Disabled cursor for form controls and buttons.
-// $cursor-disabled: not-allowed
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-// $dropdown-bg: #fff
-//** Dropdown menu `border-color`.
-// $dropdown-border: rgba(0,0,0,.15)
-//** Dropdown menu `border-color` **for IE8**.
-// $dropdown-fallback-border: #ccc
-//** Divider color for between dropdown items.
-// $dropdown-divider-bg: #e5e5e5
-
-//** Dropdown link text color.
-// $dropdown-link-color: $gray-dark
-//** Hover color for dropdown links.
-// $dropdown-link-hover-color: darken($gray-dark, 5%)
-//** Hover background for dropdown links.
-// $dropdown-link-hover-bg: #f5f5f5
-
-//** Active dropdown menu item text color.
-// $dropdown-link-active-color: $component-active-color
-//** Active dropdown menu item background color.
-// $dropdown-link-active-bg: $component-active-bg
-
-//** Disabled dropdown menu item background color.
-// $dropdown-link-disabled-color: $gray-light
-
-//** Text color for headers within dropdown menus.
-// $dropdown-header-color: $gray-light
-
-//** Deprecated `$dropdown-caret-color` as of v3.1.0
-// $dropdown-caret-color: #000
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-// $zindex-navbar: 1000
-// $zindex-dropdown: 1000
-// $zindex-popover: 1060
-// $zindex-tooltip: 1070
-// $zindex-navbar-fixed: 1030
-// $zindex-modal-background: 1040
-// $zindex-modal: 1050
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-//** Deprecated `$screen-xs` as of v3.0.1
-// $screen-xs: 480px
-//** Deprecated `$screen-xs-min` as of v3.2.0
-// $screen-xs-min: $screen-xs
-//** Deprecated `$screen-phone` as of v3.0.1
-// $screen-phone: $screen-xs-min
-
-// Small screen / tablet
-//** Deprecated `$screen-sm` as of v3.0.1
-// $screen-sm: 768px
-// $screen-sm-min: $screen-sm
-//** Deprecated `$screen-tablet` as of v3.0.1
-// $screen-tablet: $screen-sm-min
-
-// Medium screen / desktop
-//** Deprecated `$screen-md` as of v3.0.1
-// $screen-md: 992px
-// $screen-md-min: $screen-md
-//** Deprecated `$screen-desktop` as of v3.0.1
-// $screen-desktop: $screen-md-min
-
-// Large screen / wide desktop
-//** Deprecated `$screen-lg` as of v3.0.1
-// $screen-lg: 1200px
-// $screen-lg-min: $screen-lg
-//** Deprecated `$screen-lg-desktop` as of v3.0.1
-// $screen-lg-desktop: $screen-lg-min
-
-// So media queries don't overlap when required, provide a maximum
-// $screen-xs-max: ($screen-sm-min - 1)
-// $screen-sm-max: ($screen-md-min - 1)
-// $screen-md-max: ($screen-lg-min - 1)
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-// $grid-columns: 12
-//** Padding between columns. Gets divided in half for the left and right.
-// $grid-gutter-width: 30px
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-// $grid-float-breakpoint: $screen-sm-min
-//** Point at which the navbar begins collapsing.
-// $grid-float-breakpoint-max: ($grid-float-breakpoint - 1)
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-// $container-tablet: (720px + $grid-gutter-width)
-//** For `$screen-sm-min` and up.
-// $container-sm: $container-tablet
-
-// Medium screen / desktop
-// $container-desktop: (940px + $grid-gutter-width)
-//** For `$screen-md-min` and up.
-// $container-md: $container-desktop
-
-// Large screen / wide desktop
-// $container-large-desktop: (1140px + $grid-gutter-width)
-//** For `$screen-lg-min` and up.
-// $container-lg: $container-large-desktop
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-// $navbar-height: 50px
-// $navbar-margin-bottom: $line-height-computed
-// $navbar-border-radius: $border-radius-base
-// $navbar-padding-horizontal: floor(($grid-gutter-width / 2))
-// $navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2)
-// $navbar-collapse-max-height: 340px
-
-// $navbar-default-color: #777
-// $navbar-default-bg: #f8f8f8
-// $navbar-default-border: darken($navbar-default-bg, 6.5%)
-
-// Navbar links
-// $navbar-default-link-color: #777
-// $navbar-default-link-hover-color: #333
-// $navbar-default-link-hover-bg: transparent
-// $navbar-default-link-active-color: #555
-// $navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%)
-// $navbar-default-link-disabled-color: #ccc
-// $navbar-default-link-disabled-bg: transparent
-
-// Navbar brand label
-// $navbar-default-brand-color: $navbar-default-link-color
-// $navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%)
-// $navbar-default-brand-hover-bg: transparent
-
-// Navbar toggle
-// $navbar-default-toggle-hover-bg: #ddd
-// $navbar-default-toggle-icon-bar-bg: #888
-// $navbar-default-toggle-border-color: #ddd
-
-
-//=== Inverted navbar
-// Reset inverted navbar basics
-// $navbar-inverse-color: lighten($gray-light, 15%)
-// $navbar-inverse-bg: #222
-// $navbar-inverse-border: darken($navbar-inverse-bg, 10%)
-
-// Inverted navbar links
-// $navbar-inverse-link-color: lighten($gray-light, 15%)
-// $navbar-inverse-link-hover-color: #fff
-// $navbar-inverse-link-hover-bg: transparent
-// $navbar-inverse-link-active-color: $navbar-inverse-link-hover-color
-// $navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%)
-// $navbar-inverse-link-disabled-color: #444
-// $navbar-inverse-link-disabled-bg: transparent
-
-// Inverted navbar brand label
-// $navbar-inverse-brand-color: $navbar-inverse-link-color
-// $navbar-inverse-brand-hover-color: #fff
-// $navbar-inverse-brand-hover-bg: transparent
-
-// Inverted navbar toggle
-// $navbar-inverse-toggle-hover-bg: #333
-// $navbar-inverse-toggle-icon-bar-bg: #fff
-// $navbar-inverse-toggle-border-color: #333
-
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-// $nav-link-padding: 10px 15px
-// $nav-link-hover-bg: $gray-lighter
-
-// $nav-disabled-link-color: $gray-light
-// $nav-disabled-link-hover-color: $gray-light
-
-//== Tabs
-// $nav-tabs-border-color: #ddd
-
-// $nav-tabs-link-hover-border-color: $gray-lighter
-
-// $nav-tabs-active-link-hover-bg: $body-bg
-// $nav-tabs-active-link-hover-color: $gray
-// $nav-tabs-active-link-hover-border-color: #ddd
-
-// $nav-tabs-justified-link-border-color: #ddd
-// $nav-tabs-justified-active-link-border-color: $body-bg
-
-//== Pills
-// $nav-pills-border-radius: $border-radius-base
-// $nav-pills-active-link-hover-bg: $component-active-bg
-// $nav-pills-active-link-hover-color: $component-active-color
-
-
-//== Pagination
-//
-//##
-
-// $pagination-color: $link-color
-// $pagination-bg: #fff
-// $pagination-border: #ddd
-
-// $pagination-hover-color: $link-hover-color
-// $pagination-hover-bg: $gray-lighter
-// $pagination-hover-border: #ddd
-
-// $pagination-active-color: #fff
-// $pagination-active-bg: $brand-primary
-// $pagination-active-border: $brand-primary
-
-// $pagination-disabled-color: $gray-light
-// $pagination-disabled-bg: #fff
-// $pagination-disabled-border: #ddd
-
-
-//== Pager
-//
-//##
-
-// $pager-bg: $pagination-bg
-// $pager-border: $pagination-border
-// $pager-border-radius: 15px
-
-// $pager-hover-bg: $pagination-hover-bg
-
-// $pager-active-bg: $pagination-active-bg
-// $pager-active-color: $pagination-active-color
-
-// $pager-disabled-color: $pagination-disabled-color
-
-
-//== Jumbotron
-//
-//##
-
-// $jumbotron-padding: 30px
-// $jumbotron-color: inherit
-// $jumbotron-bg: $gray-lighter
-// $jumbotron-heading-color: inherit
-// $jumbotron-font-size: ceil(($font-size-base * 1.5))
-// $jumbotron-heading-font-size: ceil(($font-size-base * 4.5))
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-// $state-success-text: #3c763d
-// $state-success-bg: #dff0d8
-// $state-success-border: darken(adjust-hue($state-success-bg, -10), 5%)
-
-// $state-info-text: #31708f
-// $state-info-bg: #d9edf7
-// $state-info-border: darken(adjust-hue($state-info-bg, -10), 7%)
-
-// $state-warning-text: #8a6d3b
-// $state-warning-bg: #fcf8e3
-// $state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%)
-
-// $state-danger-text: #a94442
-// $state-danger-bg: #f2dede
-// $state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%)
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-// $tooltip-max-width: 200px
-//** Tooltip text color
-// $tooltip-color: #fff
-//** Tooltip background color
-// $tooltip-bg: #000
-// $tooltip-opacity: .9
-
-//** Tooltip arrow width
-// $tooltip-arrow-width: 5px
-//** Tooltip arrow color
-// $tooltip-arrow-color: $tooltip-bg
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-// $popover-bg: #fff
-//** Popover maximum width
-// $popover-max-width: 276px
-//** Popover border color
-// $popover-border-color: rgba(0,0,0,.2)
-//** Popover fallback border color
-// $popover-fallback-border-color: #ccc
-
-//** Popover title background color
-// $popover-title-bg: darken($popover-bg, 3%)
-
-//** Popover arrow width
-// $popover-arrow-width: 10px
-//** Popover arrow color
-// $popover-arrow-color: $popover-bg
-
-//** Popover outer arrow width
-// $popover-arrow-outer-width: ($popover-arrow-width + 1)
-//** Popover outer arrow color
-// $popover-arrow-outer-color: fade_in($popover-border-color, 0.05)
-//** Popover outer arrow fallback color
-// $popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%)
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-// $label-default-bg: $gray-light
-//** Primary label background color
-// $label-primary-bg: $brand-primary
-//** Success label background color
-// $label-success-bg: $brand-success
-//** Info label background color
-// $label-info-bg: $brand-info
-//** Warning label background color
-// $label-warning-bg: $brand-warning
-//** Danger label background color
-// $label-danger-bg: $brand-danger
-
-//** Default label text color
-// $label-color: #fff
-//** Default text color of a linked label
-// $label-link-hover-color: #fff
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-// $modal-inner-padding: 15px
-
-//** Padding applied to the modal title
-// $modal-title-padding: 15px
-//** Modal title line-height
-// $modal-title-line-height: $line-height-base
-
-//** Background color of modal content area
-// $modal-content-bg: #fff
-//** Modal content border color
-// $modal-content-border-color: rgba(0,0,0,.2)
-//** Modal content border color **for IE8**
-// $modal-content-fallback-border-color: #999
-
-//** Modal backdrop background color
-// $modal-backdrop-bg: #000
-//** Modal backdrop opacity
-// $modal-backdrop-opacity: .5
-//** Modal header border color
-// $modal-header-border-color: #e5e5e5
-//** Modal footer border color
-// $modal-footer-border-color: $modal-header-border-color
-
-// $modal-lg: 900px
-// $modal-md: 600px
-// $modal-sm: 300px
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-// $alert-padding: 15px
-// $alert-border-radius: $border-radius-base
-// $alert-link-font-weight: bold
-
-// $alert-success-bg: $state-success-bg
-// $alert-success-text: $state-success-text
-// $alert-success-border: $state-success-border
-
-// $alert-info-bg: $state-info-bg
-// $alert-info-text: $state-info-text
-// $alert-info-border: $state-info-border
-
-// $alert-warning-bg: $state-warning-bg
-// $alert-warning-text: $state-warning-text
-// $alert-warning-border: $state-warning-border
-
-// $alert-danger-bg: $state-danger-bg
-// $alert-danger-text: $state-danger-text
-// $alert-danger-border: $state-danger-border
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-// $progress-bg: #f5f5f5
-//** Progress bar text color
-// $progress-bar-color: #fff
-//** Variable for setting rounded corners on progress bar.
-// $progress-border-radius: $border-radius-base
-
-//** Default progress bar color
-// $progress-bar-bg: $brand-primary
-//** Success progress bar color
-// $progress-bar-success-bg: $brand-success
-//** Warning progress bar color
-// $progress-bar-warning-bg: $brand-warning
-//** Danger progress bar color
-// $progress-bar-danger-bg: $brand-danger
-//** Info progress bar color
-// $progress-bar-info-bg: $brand-info
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-// $list-group-bg: #fff
-//** `.list-group-item` border color
-// $list-group-border: #ddd
-//** List group border radius
-// $list-group-border-radius: $border-radius-base
-
-//** Background color of single list items on hover
-// $list-group-hover-bg: #f5f5f5
-//** Text color of active list items
-// $list-group-active-color: $component-active-color
-//** Background color of active list items
-// $list-group-active-bg: $component-active-bg
-//** Border color of active list elements
-// $list-group-active-border: $list-group-active-bg
-//** Text color for content within active list items
-// $list-group-active-text-color: lighten($list-group-active-bg, 40%)
-
-//** Text color of disabled list items
-// $list-group-disabled-color: $gray-light
-//** Background color of disabled list items
-// $list-group-disabled-bg: $gray-lighter
-//** Text color for content within disabled list items
-// $list-group-disabled-text-color: $list-group-disabled-color
-
-// $list-group-link-color: #555
-// $list-group-link-hover-color: $list-group-link-color
-// $list-group-link-heading-color: #333
-
-
-//== Panels
-//
-//##
-
-// $panel-bg: #fff
-// $panel-body-padding: 15px
-// $panel-heading-padding: 10px 15px
-// $panel-footer-padding: $panel-heading-padding
-// $panel-border-radius: $border-radius-base
-
-//** Border color for elements within panels
-// $panel-inner-border: #ddd
-// $panel-footer-bg: #f5f5f5
-
-// $panel-default-text: $gray-dark
-// $panel-default-border: #ddd
-// $panel-default-heading-bg: #f5f5f5
-
-// $panel-primary-text: #fff
-// $panel-primary-border: $brand-primary
-// $panel-primary-heading-bg: $brand-primary
-
-// $panel-success-text: $state-success-text
-// $panel-success-border: $state-success-border
-// $panel-success-heading-bg: $state-success-bg
-
-// $panel-info-text: $state-info-text
-// $panel-info-border: $state-info-border
-// $panel-info-heading-bg: $state-info-bg
-
-// $panel-warning-text: $state-warning-text
-// $panel-warning-border: $state-warning-border
-// $panel-warning-heading-bg: $state-warning-bg
-
-// $panel-danger-text: $state-danger-text
-// $panel-danger-border: $state-danger-border
-// $panel-danger-heading-bg: $state-danger-bg
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-// $thumbnail-padding: 4px
-//** Thumbnail background color
-// $thumbnail-bg: $body-bg
-//** Thumbnail border color
-// $thumbnail-border: #ddd
-//** Thumbnail border radius
-// $thumbnail-border-radius: $border-radius-base
-
-//** Custom text color for thumbnail captions
-// $thumbnail-caption-color: $text-color
-//** Padding around the thumbnail caption
-// $thumbnail-caption-padding: 9px
-
-
-//== Wells
-//
-//##
-
-// $well-bg: #f5f5f5
-// $well-border: darken($well-bg, 7%)
-
-
-//== Badges
-//
-//##
-
-// $badge-color: #fff
-//** Linked badge text color on hover
-// $badge-link-hover-color: #fff
-// $badge-bg: $gray-light
-
-//** Badge text color in active nav link
-// $badge-active-color: $link-color
-//** Badge background color in active nav link
-// $badge-active-bg: #fff
-
-// $badge-font-weight: bold
-// $badge-line-height: 1
-// $badge-border-radius: 10px
-
-
-//== Breadcrumbs
-//
-//##
-
-// $breadcrumb-padding-vertical: 8px
-// $breadcrumb-padding-horizontal: 15px
-//** Breadcrumb background color
-// $breadcrumb-bg: #f5f5f5
-//** Breadcrumb text color
-// $breadcrumb-color: #ccc
-//** Text color of current page in the breadcrumb
-// $breadcrumb-active-color: $gray-light
-//** Textual separator for between breadcrumb elements
-// $breadcrumb-separator: "/"
-
-
-//== Carousel
-//
-//##
-
-// $carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6)
-
-// $carousel-control-color: #fff
-// $carousel-control-width: 15%
-// $carousel-control-opacity: .5
-// $carousel-control-font-size: 20px
-
-// $carousel-indicator-active-bg: #fff
-// $carousel-indicator-border-color: #fff
-
-// $carousel-caption-color: #fff
-
-
-//== Close
-//
-//##
-
-// $close-font-weight: bold
-// $close-color: #000
-// $close-text-shadow: 0 1px 0 #fff
-
-
-//== Code
-//
-//##
-
-// $code-color: #c7254e
-// $code-bg: #f9f2f4
-
-// $kbd-color: #fff
-// $kbd-bg: #333
-
-// $pre-bg: #f5f5f5
-// $pre-color: $gray-dark
-// $pre-border-color: #ccc
-// $pre-scrollable-max-height: 340px
-
-
-//== Type
-//
-//##
-
-//** Horizontal offset for forms and lists.
-// $component-offset-horizontal: 180px
-//** Text muted color
-// $text-muted: $gray-light
-//** Abbreviations and acronyms border color
-// $abbr-border-color: $gray-light
-//** Headings small color
-// $headings-small-color: $gray-light
-//** Blockquote small color
-// $blockquote-small-color: $gray-light
-//** Blockquote font size
-// $blockquote-font-size: ($font-size-base * 1.25)
-//** Blockquote border color
-// $blockquote-border-color: $gray-lighter
-//** Page header border color
-// $page-header-border-color: $gray-lighter
-//** Width of horizontal description list titles
-// $dl-horizontal-offset: $component-offset-horizontal
-//** Point at which .dl-horizontal becomes horizontal
-// $dl-horizontal-breakpoint: $grid-float-breakpoint
-//** Horizontal line color.
-// $hr-border: $gray-lighter
+++ /dev/null
-.span_action {
- margin: 5px;
- cursor: pointer;
-}
-
-header div {
- height: 50px;
- line-height: 25px;
- margin-bottom: 50px;
-}
-
-menu {
- margin-right: 20px;
- border-right: 1px solid rgba(0, 0, 0, 0.2);
-}
-
-menu .panel_button {
- margin: 8px;
- cursor: pointer;
- transition: margin 0.2s;
-}
-
-menu .panel_button:hover {
- margin-left: 15px;
-}
-
-menu .glyphicon {
- margin: 5px;
-}
-
-#ajax_load {
- min-height: 500px;
-}
-
-.loading {
- display: inline-block;
- margin-top: 100px;
-}
-
-.video {
- margin-bottom: 10px;
- transition: margin 0.5s ease;
-}
-
-.video:hover {
- margin-left: 5px;
-}
-
-.video_name {
- cursor: pointer;
- margin-right: 5px;
-}
-
-.video_pod_url {
- font-size: small;
- color: rgba(0, 0, 0, 0.5);
-}
-
-.video_description {
- font-size: small;
- font-style: italic;
- margin-left: 7px;
-}
-
-.btn-file {
- position: relative;
- overflow: hidden;
-}
-.btn-file input[type=file] {
- position: absolute;
- top: 0;
- right: 0;
- min-width: 100%;
- min-height: 100%;
- font-size: 100px;
- text-align: right;
- filter: alpha(opacity=0);
- opacity: 0;
- outline: none;
- background: white;
- cursor: inherit;
- display: block;
-}
-
-.name_file {
- display: inline-block;
- margin-left: 10px;
-}
\ No newline at end of file
var app = express()
// ----------- Checker -----------
-var checker = require('./initializers/checker')
+var checker = require('./server/initializers/checker')
var miss = checker.checkConfig()
if (miss.length !== 0) {
// ----------- PeerTube modules -----------
var config = require('config')
-var constants = require('./initializers/constants')
-var customValidators = require('./helpers/customValidators')
-var database = require('./initializers/database')
-var logger = require('./helpers/logger')
-var peertubeCrypto = require('./helpers/peertubeCrypto')
-var poolRequests = require('./lib/poolRequests')
-var routes = require('./controllers')
-var utils = require('./helpers/utils')
-var videos = require('./lib/videos')
-var webtorrent = require('./lib/webtorrent')
+var constants = require('./server/initializers/constants')
+var customValidators = require('./server/helpers/customValidators')
+var database = require('./server/initializers/database')
+var logger = require('./server/helpers/logger')
+var peertubeCrypto = require('./server/helpers/peertubeCrypto')
+var poolRequests = require('./server/lib/poolRequests')
+var routes = require('./server/controllers')
+var utils = require('./server/helpers/utils')
+var videos = require('./server/lib/videos')
+var webtorrent = require('./server/lib/webtorrent')
// Get configurations
var port = config.get('listen.port')
require('segfault-handler').registerHandler()
// Static files
-app.use(express.static(path.join(__dirname, '/public'), { maxAge: 0 }))
-
-// Jade template from ./views directory
-app.set('views', path.join(__dirname, '/views'))
-app.set('view engine', 'jade')
+app.use(express.static(path.join(__dirname, '/app'), { maxAge: 0 }))
// API routes
var api_route = '/api/' + constants.API_VERSION
--- /dev/null
+'use strict'
+
+var express = require('express')
+
+var router = express.Router()
+
+var podsController = require('./pods')
+var remoteVideosController = require('./remoteVideos')
+var videosController = require('./videos')
+
+router.use('/pods', podsController)
+router.use('/remotevideos', remoteVideosController)
+router.use('/videos', videosController)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
--- /dev/null
+'use strict'
+
+var express = require('express')
+var fs = require('fs')
+
+var logger = require('../../../helpers/logger')
+var friends = require('../../../lib/friends')
+var middleware = require('../../../middlewares')
+var cacheMiddleware = middleware.cache
+var peertubeCrypto = require('../../../helpers/peertubeCrypto')
+var Pods = require('../../../models/pods')
+var reqValidator = middleware.reqValidators.pods
+var secureMiddleware = middleware.secure
+var secureRequest = middleware.reqValidators.remote.secureRequest
+var Videos = require('../../../models/videos')
+
+var router = express.Router()
+
+router.get('/', cacheMiddleware.cache(false), listPods)
+router.post('/', reqValidator.podsAdd, cacheMiddleware.cache(false), addPods)
+router.get('/makefriends', reqValidator.makeFriends, cacheMiddleware.cache(false), makeFriends)
+router.get('/quitfriends', cacheMiddleware.cache(false), quitFriends)
+// Post because this is a secured request
+router.post('/remove', secureRequest, secureMiddleware.decryptBody, removePods)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function addPods (req, res, next) {
+ var informations = req.body.data
+ Pods.add(informations, function (err) {
+ if (err) return next(err)
+
+ Videos.addRemotes(informations.videos)
+
+ fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
+ if (err) {
+ logger.error('Cannot read cert file.')
+ return next(err)
+ }
+
+ Videos.listOwned(function (err, videos_list) {
+ if (err) {
+ logger.error('Cannot get the list of owned videos.')
+ return next(err)
+ }
+
+ res.json({ cert: cert, videos: videos_list })
+ })
+ })
+ })
+}
+
+function listPods (req, res, next) {
+ Pods.list(function (err, pods_list) {
+ if (err) return next(err)
+
+ res.json(pods_list)
+ })
+}
+
+function makeFriends (req, res, next) {
+ friends.makeFriends(function (err) {
+ if (err) return next(err)
+
+ res.sendStatus(204)
+ })
+}
+
+function removePods (req, res, next) {
+ var url = req.body.signature.url
+ Pods.remove(url, function (err) {
+ if (err) return next(err)
+
+ Videos.removeAllRemotesOf(url, function (err) {
+ if (err) logger.error('Cannot remove all remote videos of %s.', url)
+ else logger.info('%s pod removed.', url)
+
+ res.sendStatus(204)
+ })
+ })
+}
+
+function quitFriends (req, res, next) {
+ friends.quitFriends(function (err) {
+ if (err) return next(err)
+
+ res.sendStatus(204)
+ })
+}
--- /dev/null
+'use strict'
+
+var express = require('express')
+var pluck = require('lodash-node/compat/collection/pluck')
+
+var middleware = require('../../../middlewares')
+var secureMiddleware = middleware.secure
+var cacheMiddleware = middleware.cache
+var reqValidator = middleware.reqValidators.remote
+var videos = require('../../../models/videos')
+
+var router = express.Router()
+
+router.post('/add',
+ reqValidator.secureRequest,
+ secureMiddleware.decryptBody,
+ reqValidator.remoteVideosAdd,
+ cacheMiddleware.cache(false),
+ addRemoteVideos
+)
+
+router.post('/remove',
+ reqValidator.secureRequest,
+ secureMiddleware.decryptBody,
+ reqValidator.remoteVideosRemove,
+ cacheMiddleware.cache(false),
+ removeRemoteVideo
+)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function addRemoteVideos (req, res, next) {
+ videos.addRemotes(req.body.data, function (err, videos) {
+ if (err) return next(err)
+
+ res.json(videos)
+ })
+}
+
+function removeRemoteVideo (req, res, next) {
+ var url = req.body.signature.url
+ var magnetUris = pluck(req.body.data, 'magnetUri')
+
+ videos.removeRemotesOfByMagnetUris(url, magnetUris, function (err) {
+ if (err) return next(err)
+
+ res.sendStatus(204)
+ })
+}
--- /dev/null
+'use strict'
+
+var config = require('config')
+var crypto = require('crypto')
+var express = require('express')
+var multer = require('multer')
+
+var logger = require('../../../helpers/logger')
+var friends = require('../../../lib/friends')
+var middleware = require('../../../middlewares')
+var cacheMiddleware = middleware.cache
+var reqValidator = middleware.reqValidators.videos
+var Videos = require('../../../models/videos') // model
+var videos = require('../../../lib/videos')
+var webtorrent = require('../../../lib/webtorrent')
+
+var router = express.Router()
+var uploads = config.get('storage.uploads')
+
+// multer configuration
+var storage = multer.diskStorage({
+ destination: function (req, file, cb) {
+ cb(null, uploads)
+ },
+
+ filename: function (req, file, cb) {
+ var extension = ''
+ if (file.mimetype === 'video/webm') extension = 'webm'
+ else if (file.mimetype === 'video/mp4') extension = 'mp4'
+ else if (file.mimetype === 'video/ogg') extension = 'ogv'
+ crypto.pseudoRandomBytes(16, function (err, raw) {
+ var fieldname = err ? undefined : raw.toString('hex')
+ cb(null, fieldname + '.' + extension)
+ })
+ }
+})
+
+var reqFiles = multer({ storage: storage }).fields([{ name: 'input_video', maxCount: 1 }])
+
+router.get('/', cacheMiddleware.cache(false), listVideos)
+router.post('/', reqFiles, reqValidator.videosAdd, cacheMiddleware.cache(false), addVideo)
+router.get('/:id', reqValidator.videosGet, cacheMiddleware.cache(false), getVideos)
+router.delete('/:id', reqValidator.videosRemove, cacheMiddleware.cache(false), removeVideo)
+router.get('/search/:name', reqValidator.videosSearch, cacheMiddleware.cache(false), searchVideos)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function addVideo (req, res, next) {
+ var video_file = req.files.input_video[0]
+ var video_infos = req.body
+
+ videos.seed(video_file.path, function (err, torrent) {
+ if (err) {
+ logger.error('Cannot seed this video.')
+ return next(err)
+ }
+
+ var video_data = {
+ name: video_infos.name,
+ namePath: video_file.filename,
+ description: video_infos.description,
+ magnetUri: torrent.magnetURI
+ }
+
+ Videos.add(video_data, function (err) {
+ if (err) {
+ // TODO unseed the video
+ logger.error('Cannot insert this video in the database.')
+ return next(err)
+ }
+
+ // Now we'll add the video's meta data to our friends
+ friends.addVideoToFriends(video_data)
+
+ // TODO : include Location of the new video
+ res.sendStatus(201)
+ })
+ })
+}
+
+function getVideos (req, res, next) {
+ Videos.get(req.params.id, function (err, video) {
+ if (err) return next(err)
+
+ if (video === null) {
+ return res.sendStatus(404)
+ }
+
+ res.json(video)
+ })
+}
+
+function listVideos (req, res, next) {
+ Videos.list(function (err, videos_list) {
+ if (err) return next(err)
+
+ res.json(videos_list)
+ })
+}
+
+function removeVideo (req, res, next) {
+ var video_id = req.params.id
+ Videos.get(video_id, function (err, video) {
+ if (err) return next(err)
+
+ removeTorrent(video.magnetUri, function () {
+ Videos.removeOwned(req.params.id, function (err) {
+ if (err) return next(err)
+
+ var params = {
+ name: video.name,
+ magnetUri: video.magnetUri
+ }
+
+ friends.removeVideoToFriends(params)
+ res.sendStatus(204)
+ })
+ })
+ })
+}
+
+function searchVideos (req, res, next) {
+ Videos.search(req.params.name, function (err, videos_list) {
+ if (err) return next(err)
+
+ res.json(videos_list)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
+function removeTorrent (magnetUri, callback) {
+ try {
+ webtorrent.remove(magnetUri, callback)
+ } catch (err) {
+ logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
+ return callback(null)
+ }
+}
--- /dev/null
+'use strict'
+
+var constants = require('../initializers/constants')
+
+var apiController = require('./api/' + constants.API_VERSION)
+var viewsController = require('./views')
+
+module.exports = {
+ api: apiController,
+ views: viewsController
+}
--- /dev/null
+'use strict'
+
+var express = require('express')
+
+var cacheMiddleware = require('../middlewares').cache
+
+var router = express.Router()
+
+router.get(/^\/(index)?$/, cacheMiddleware.cache(), getIndex)
+router.get('/partials/:directory/:name', cacheMiddleware.cache(), getPartial)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function getIndex (req, res) {
+ res.render('index')
+}
+
+function getPartial (req, res) {
+ var directory = req.params.directory
+ var name = req.params.name
+
+ res.render('partials/' + directory + '/' + name)
+}
--- /dev/null
+'use strict'
+
+var validator = require('validator')
+
+var customValidators = {
+ eachIsRemoteVideosAddValid: eachIsRemoteVideosAddValid,
+ eachIsRemoteVideosRemoveValid: eachIsRemoteVideosRemoveValid,
+ isArray: isArray
+}
+
+function eachIsRemoteVideosAddValid (values) {
+ return values.every(function (val) {
+ return validator.isLength(val.name, 1, 50) &&
+ validator.isLength(val.description, 1, 50) &&
+ validator.isLength(val.magnetUri, 10) &&
+ validator.isURL(val.podUrl)
+ })
+}
+
+function eachIsRemoteVideosRemoveValid (values) {
+ return values.every(function (val) {
+ return validator.isLength(val.magnetUri, 10)
+ })
+}
+
+function isArray (value) {
+ return Array.isArray(value)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = customValidators
--- /dev/null
+// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
+'use strict'
+
+var config = require('config')
+var path = require('path')
+var winston = require('winston')
+winston.emitErrs = true
+
+var logDir = path.join(__dirname, '..', config.get('storage.logs'))
+var logger = new winston.Logger({
+ transports: [
+ new winston.transports.File({
+ level: 'debug',
+ filename: path.join(logDir, 'all-logs.log'),
+ handleExceptions: true,
+ json: true,
+ maxsize: 5242880,
+ maxFiles: 5,
+ colorize: false
+ }),
+ new winston.transports.Console({
+ level: 'debug',
+ handleExceptions: true,
+ humanReadableUnhandledException: true,
+ json: false,
+ colorize: true
+ })
+ ],
+ exitOnError: true
+})
+
+logger.stream = {
+ write: function (message, encoding) {
+ logger.info(message)
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = logger
--- /dev/null
+'use strict'
+
+var config = require('config')
+var crypto = require('crypto')
+var fs = require('fs')
+var openssl = require('openssl-wrapper')
+var path = require('path')
+var ursa = require('ursa')
+
+var logger = require('./logger')
+
+var certDir = path.join(__dirname, '..', config.get('storage.certs'))
+var algorithm = 'aes-256-ctr'
+
+var peertubeCrypto = {
+ checkSignature: checkSignature,
+ createCertsIfNotExist: createCertsIfNotExist,
+ decrypt: decrypt,
+ encrypt: encrypt,
+ getCertDir: getCertDir,
+ sign: sign
+}
+
+function checkSignature (public_key, raw_data, hex_signature) {
+ var crt = ursa.createPublicKey(public_key)
+ var is_valid = crt.hashAndVerify('sha256', new Buffer(raw_data).toString('hex'), hex_signature, 'hex')
+ return is_valid
+}
+
+function createCertsIfNotExist (callback) {
+ certsExist(function (exist) {
+ if (exist === true) {
+ return callback(null)
+ }
+
+ createCerts(function (err) {
+ return callback(err)
+ })
+ })
+}
+
+function decrypt (key, data, callback) {
+ fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) {
+ if (err) return callback(err)
+
+ var my_private_key = ursa.createPrivateKey(file)
+ var decrypted_key = my_private_key.decrypt(key, 'hex', 'utf8')
+ var decrypted_data = symetricDecrypt(data, decrypted_key)
+
+ return callback(null, decrypted_data)
+ })
+}
+
+function encrypt (public_key, data, callback) {
+ var crt = ursa.createPublicKey(public_key)
+
+ symetricEncrypt(data, function (err, dataEncrypted) {
+ if (err) return callback(err)
+
+ var key = crt.encrypt(dataEncrypted.password, 'utf8', 'hex')
+ var encrypted = {
+ data: dataEncrypted.crypted,
+ key: key
+ }
+
+ callback(null, encrypted)
+ })
+}
+
+function getCertDir () {
+ return certDir
+}
+
+function sign (data) {
+ var myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem'))
+ var signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex')
+
+ return signature
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = peertubeCrypto
+
+// ---------------------------------------------------------------------------
+
+function certsExist (callback) {
+ fs.exists(certDir + 'peertube.key.pem', function (exists) {
+ return callback(exists)
+ })
+}
+
+function createCerts (callback) {
+ certsExist(function (exist) {
+ if (exist === true) {
+ var string = 'Certs already exist.'
+ logger.warning(string)
+ return callback(new Error(string))
+ }
+
+ logger.info('Generating a RSA key...')
+ openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) {
+ if (err) {
+ logger.error('Cannot create private key on this pod.')
+ return callback(err)
+ }
+ logger.info('RSA key generated.')
+
+ logger.info('Manage public key...')
+ openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) {
+ if (err) {
+ logger.error('Cannot create public key on this pod.')
+ return callback(err)
+ }
+
+ logger.info('Public key managed.')
+ return callback(null)
+ })
+ })
+ })
+}
+
+function generatePassword (callback) {
+ crypto.randomBytes(32, function (err, buf) {
+ if (err) return callback(err)
+
+ callback(null, buf.toString('utf8'))
+ })
+}
+
+function symetricDecrypt (text, password) {
+ var decipher = crypto.createDecipher(algorithm, password)
+ var dec = decipher.update(text, 'hex', 'utf8')
+ dec += decipher.final('utf8')
+ return dec
+}
+
+function symetricEncrypt (text, callback) {
+ generatePassword(function (err, password) {
+ if (err) return callback(err)
+
+ var cipher = crypto.createCipher(algorithm, password)
+ var crypted = cipher.update(text, 'utf8', 'hex')
+ crypted += cipher.final('hex')
+ callback(null, { crypted: crypted, password: password })
+ })
+}
--- /dev/null
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var request = require('request')
+var replay = require('request-replay')
+
+var constants = require('../initializers/constants')
+var logger = require('./logger')
+var peertubeCrypto = require('./peertubeCrypto')
+
+var http = config.get('webserver.https') ? 'https' : 'http'
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+
+var requests = {
+ makeMultipleRetryRequest: makeMultipleRetryRequest
+}
+
+function makeMultipleRetryRequest (all_data, pods, callbackEach, callback) {
+ if (!callback) {
+ callback = callbackEach
+ callbackEach = null
+ }
+
+ var url = http + '://' + host + ':' + port
+ var signature
+
+ // Add signature if it is specified in the params
+ if (all_data.method === 'POST' && all_data.data && all_data.sign === true) {
+ signature = peertubeCrypto.sign(url)
+ }
+
+ // Make a request for each pod
+ async.each(pods, function (pod, callback_each_async) {
+ function callbackEachRetryRequest (err, response, body, url, pod) {
+ if (callbackEach !== null) {
+ callbackEach(err, response, body, url, pod, function () {
+ callback_each_async()
+ })
+ } else {
+ callback_each_async()
+ }
+ }
+
+ var params = {
+ url: pod.url + all_data.path,
+ method: all_data.method
+ }
+
+ // Add data with POST requst ?
+ if (all_data.method === 'POST' && all_data.data) {
+ // Encrypt data ?
+ if (all_data.encrypt === true) {
+ // TODO: ES6 with let
+ ;(function (copy_params, copy_url, copy_pod, copy_signature) {
+ peertubeCrypto.encrypt(pod.publicKey, JSON.stringify(all_data.data), function (err, encrypted) {
+ if (err) return callback(err)
+
+ copy_params.json = {
+ data: encrypted.data,
+ key: encrypted.key
+ }
+
+ makeRetryRequest(copy_params, copy_url, copy_pod, copy_signature, callbackEachRetryRequest)
+ })
+ })(params, url, pod, signature)
+ } else {
+ params.json = { data: all_data.data }
+ makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
+ }
+ } else {
+ makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
+ }
+ }, callback)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = requests
+
+// ---------------------------------------------------------------------------
+
+function makeRetryRequest (params, from_url, to_pod, signature, callbackEach) {
+ // Append the signature
+ if (signature) {
+ params.json.signature = {
+ url: from_url,
+ signature: signature
+ }
+ }
+
+ logger.debug('Make retry requests to %s.', to_pod.url)
+
+ replay(
+ request.post(params, function (err, response, body) {
+ callbackEach(err, response, body, params.url, to_pod)
+ }),
+ {
+ retries: constants.REQUEST_RETRIES,
+ factor: 3,
+ maxTimeout: Infinity,
+ errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
+ }
+ ).on('replay', function (replay) {
+ logger.info('Replaying request to %s. Request failed: %d %s. Replay number: #%d. Will retry in: %d ms.',
+ params.url, replay.error.code, replay.error.message, replay.number, replay.delay)
+ })
+}
--- /dev/null
+'use strict'
+
+var logger = require('./logger')
+
+var utils = {
+ cleanForExit: cleanForExit
+}
+
+function cleanForExit (webtorrent_process) {
+ logger.info('Gracefully exiting.')
+ process.kill(-webtorrent_process.pid)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = utils
--- /dev/null
+'use strict'
+
+var config = require('config')
+var mkdirp = require('mkdirp')
+var path = require('path')
+
+var checker = {
+ checkConfig: checkConfig,
+ createDirectoriesIfNotExist: createDirectoriesIfNotExist
+}
+
+// Check the config files
+function checkConfig () {
+ var required = [ 'listen.port',
+ 'webserver.https', 'webserver.host', 'webserver.port',
+ 'database.host', 'database.port', 'database.suffix',
+ 'storage.certs', 'storage.uploads', 'storage.logs',
+ 'network.friends' ]
+ var miss = []
+
+ for (var key of required) {
+ if (!config.has(key)) {
+ miss.push(key)
+ }
+ }
+
+ return miss
+}
+
+// Create directories for the storage if it doesn't exist
+function createDirectoriesIfNotExist () {
+ var storages = config.get('storage')
+
+ for (var key of Object.keys(storages)) {
+ var dir = storages[key]
+ try {
+ mkdirp.sync(path.join(__dirname, '..', dir))
+ } catch (error) {
+ throw new Error('Cannot create ' + path + ':' + error)
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = checker
--- /dev/null
+'use strict'
+
+// API version of our pod
+var API_VERSION = 'v1'
+
+// Score a pod has when we create it as a friend
+var FRIEND_BASE_SCORE = 100
+
+// Time to wait between requests to the friends
+var INTERVAL = 60000
+
+// Number of points we add/remove from a friend after a successful/bad request
+var PODS_SCORE = {
+ MALUS: -10,
+ BONUS: 10
+}
+
+// Number of retries we make for the make retry requests (to friends...)
+var REQUEST_RETRIES = 10
+
+// Special constants for a test instance
+if (isTestInstance() === true) {
+ FRIEND_BASE_SCORE = 20
+ INTERVAL = 10000
+ REQUEST_RETRIES = 2
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = {
+ API_VERSION: API_VERSION,
+ FRIEND_BASE_SCORE: FRIEND_BASE_SCORE,
+ INTERVAL: INTERVAL,
+ PODS_SCORE: PODS_SCORE,
+ REQUEST_RETRIES: REQUEST_RETRIES
+}
+
+// ---------------------------------------------------------------------------
+
+function isTestInstance () {
+ return (process.env.NODE_ENV === 'test')
+}
--- /dev/null
+'use strict'
+
+var config = require('config')
+var mongoose = require('mongoose')
+
+var logger = require('../helpers/logger')
+
+var dbname = 'peertube' + config.get('database.suffix')
+var host = config.get('database.host')
+var port = config.get('database.port')
+
+var database = {
+ connect: connect
+}
+
+function connect () {
+ mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname)
+ mongoose.connection.on('error', function () {
+ throw new Error('Mongodb connection error.')
+ })
+
+ mongoose.connection.on('open', function () {
+ logger.info('Connected to mongodb.')
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = database
--- /dev/null
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var fs = require('fs')
+var request = require('request')
+
+var constants = require('../initializers/constants')
+var logger = require('../helpers/logger')
+var peertubeCrypto = require('../helpers/peertubeCrypto')
+var Pods = require('../models/pods')
+var poolRequests = require('../lib/poolRequests')
+var requests = require('../helpers/requests')
+var Videos = require('../models/videos')
+
+var http = config.get('webserver.https') ? 'https' : 'http'
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+
+var pods = {
+ addVideoToFriends: addVideoToFriends,
+ hasFriends: hasFriends,
+ makeFriends: makeFriends,
+ quitFriends: quitFriends,
+ removeVideoToFriends: removeVideoToFriends
+}
+
+function addVideoToFriends (video) {
+ // To avoid duplicates
+ var id = video.name + video.magnetUri
+ // ensure namePath is null
+ video.namePath = null
+ poolRequests.addRequest(id, 'add', video)
+}
+
+function hasFriends (callback) {
+ Pods.count(function (err, count) {
+ if (err) return callback(err)
+
+ var has_friends = (count !== 0)
+ callback(null, has_friends)
+ })
+}
+
+function makeFriends (callback) {
+ var pods_score = {}
+
+ logger.info('Make friends!')
+ fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
+ if (err) {
+ logger.error('Cannot read public cert.')
+ return callback(err)
+ }
+
+ var urls = config.get('network.friends')
+
+ async.each(urls, function (url, callback) {
+ computeForeignPodsList(url, pods_score, callback)
+ }, function (err) {
+ if (err) return callback(err)
+
+ logger.debug('Pods scores computed.', { pods_score: pods_score })
+ var pods_list = computeWinningPods(urls, pods_score)
+ logger.debug('Pods that we keep computed.', { pods_to_keep: pods_list })
+
+ makeRequestsToWinningPods(cert, pods_list, callback)
+ })
+ })
+}
+
+function quitFriends (callback) {
+ // Stop pool requests
+ poolRequests.deactivate()
+ // Flush pool requests
+ poolRequests.forceSend()
+
+ Pods.list(function (err, pods) {
+ if (err) return callback(err)
+
+ var request = {
+ method: 'POST',
+ path: '/api/' + constants.API_VERSION + '/pods/remove',
+ sign: true,
+ encrypt: true,
+ data: {
+ url: 'me' // Fake data
+ }
+ }
+
+ // Announce we quit them
+ requests.makeMultipleRetryRequest(request, pods, function () {
+ Pods.removeAll(function (err) {
+ poolRequests.activate()
+
+ if (err) return callback(err)
+
+ logger.info('Broke friends, so sad :(')
+
+ Videos.removeAllRemotes(function (err) {
+ if (err) return callback(err)
+
+ logger.info('Removed all remote videos.')
+ callback(null)
+ })
+ })
+ })
+ })
+}
+
+function removeVideoToFriends (video) {
+ // To avoid duplicates
+ var id = video.name + video.magnetUri
+ poolRequests.addRequest(id, 'remove', video)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = pods
+
+// ---------------------------------------------------------------------------
+
+function computeForeignPodsList (url, pods_score, callback) {
+ // Let's give 1 point to the pod we ask the friends list
+ pods_score[url] = 1
+
+ getForeignPodsList(url, function (err, foreign_pods_list) {
+ if (err) return callback(err)
+ if (foreign_pods_list.length === 0) return callback()
+
+ async.each(foreign_pods_list, function (foreign_pod, callback_each) {
+ var foreign_url = foreign_pod.url
+
+ if (pods_score[foreign_url]) pods_score[foreign_url]++
+ else pods_score[foreign_url] = 1
+
+ callback_each()
+ }, function () {
+ callback()
+ })
+ })
+}
+
+function computeWinningPods (urls, pods_score) {
+ // Build the list of pods to add
+ // Only add a pod if it exists in more than a half base pods
+ var pods_list = []
+ var base_score = urls.length / 2
+ Object.keys(pods_score).forEach(function (pod) {
+ if (pods_score[pod] > base_score) pods_list.push({ url: pod })
+ })
+
+ return pods_list
+}
+
+function getForeignPodsList (url, callback) {
+ var path = '/api/' + constants.API_VERSION + '/pods'
+
+ request.get(url + path, function (err, response, body) {
+ if (err) return callback(err)
+
+ callback(null, JSON.parse(body))
+ })
+}
+
+function makeRequestsToWinningPods (cert, pods_list, callback) {
+ // Stop pool requests
+ poolRequests.deactivate()
+ // Flush pool requests
+ poolRequests.forceSend()
+
+ // Get the list of our videos to send to our new friends
+ Videos.listOwned(function (err, videos_list) {
+ if (err) {
+ logger.error('Cannot get the list of videos we own.')
+ return callback(err)
+ }
+
+ var data = {
+ url: http + '://' + host + ':' + port,
+ publicKey: cert,
+ videos: videos_list
+ }
+
+ requests.makeMultipleRetryRequest(
+ { method: 'POST', path: '/api/' + constants.API_VERSION + '/pods/', data: data },
+
+ pods_list,
+
+ function eachRequest (err, response, body, url, pod, callback_each_request) {
+ // We add the pod if it responded correctly with its public certificate
+ if (!err && response.statusCode === 200) {
+ Pods.add({ url: pod.url, publicKey: body.cert, score: constants.FRIEND_BASE_SCORE }, function (err) {
+ if (err) {
+ logger.error('Error with adding %s pod.', pod.url, { error: err })
+ return callback_each_request()
+ }
+
+ Videos.addRemotes(body.videos, function (err) {
+ if (err) {
+ logger.error('Error with adding videos of pod.', pod.url, { error: err })
+ return callback_each_request()
+ }
+
+ logger.debug('Adding remote videos from %s.', pod.url, { videos: body.videos })
+ return callback_each_request()
+ })
+ })
+ } else {
+ logger.error('Error with adding %s pod.', pod.url, { error: err || new Error('Status not 200') })
+ return callback_each_request()
+ }
+ },
+
+ function endRequests (err) {
+ // Now we made new friends, we can re activate the pool of requests
+ poolRequests.activate()
+
+ if (err) {
+ logger.error('There was some errors when we wanted to make friends.')
+ return callback(err)
+ }
+
+ logger.debug('makeRequestsToWinningPods finished.')
+ return callback(null)
+ }
+ )
+ })
+}
--- /dev/null
+'use strict'
+
+var async = require('async')
+var pluck = require('lodash-node/compat/collection/pluck')
+
+var constants = require('../initializers/constants')
+var logger = require('../helpers/logger')
+var Pods = require('../models/pods')
+var PoolRequests = require('../models/poolRequests')
+var requests = require('../helpers/requests')
+var Videos = require('../models/videos')
+
+var timer = null
+
+var poolRequests = {
+ activate: activate,
+ addRequest: addRequest,
+ deactivate: deactivate,
+ forceSend: forceSend
+}
+
+function activate () {
+ logger.info('Pool requests activated.')
+ timer = setInterval(makePoolRequests, constants.INTERVAL)
+}
+
+function addRequest (id, type, request) {
+ logger.debug('Add request to the pool requests.', { id: id, type: type, request: request })
+
+ PoolRequests.findById(id, function (err, entity) {
+ if (err) {
+ logger.error('Cannot find one pool request.', { error: err })
+ return // Abort
+ }
+
+ if (entity) {
+ if (entity.type === type) {
+ logger.error('Cannot insert two same requests.')
+ return // Abort
+ }
+
+ // Remove the request of the other type
+ PoolRequests.removeRequestById(id, function (err) {
+ if (err) {
+ logger.error('Cannot remove a pool request.', { error: err })
+ return // Abort
+ }
+ })
+ } else {
+ PoolRequests.create(id, type, request, function (err) {
+ if (err) logger.error('Cannot create a pool request.', { error: err })
+ return // Abort
+ })
+ }
+ })
+}
+
+function deactivate () {
+ logger.info('Pool requests deactivated.')
+ clearInterval(timer)
+}
+
+function forceSend () {
+ logger.info('Force pool requests sending.')
+ makePoolRequests()
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = poolRequests
+
+// ---------------------------------------------------------------------------
+
+function makePoolRequest (type, requests_to_make, callback) {
+ if (!callback) callback = function () {}
+
+ Pods.list(function (err, pods) {
+ if (err) return callback(err)
+
+ var params = {
+ encrypt: true,
+ sign: true,
+ method: 'POST',
+ path: null,
+ data: requests_to_make
+ }
+
+ if (type === 'add') {
+ params.path = '/api/' + constants.API_VERSION + '/remotevideos/add'
+ } else if (type === 'remove') {
+ params.path = '/api/' + constants.API_VERSION + '/remotevideos/remove'
+ } else {
+ return callback(new Error('Unkown pool request type.'))
+ }
+
+ var bad_pods = []
+ var good_pods = []
+
+ requests.makeMultipleRetryRequest(params, pods, callbackEachPodFinished, callbackAllPodsFinished)
+
+ function callbackEachPodFinished (err, response, body, url, pod, callback_each_pod_finished) {
+ if (err || (response.statusCode !== 200 && response.statusCode !== 204)) {
+ bad_pods.push(pod._id)
+ logger.error('Error sending secure request to %s pod.', url, { error: err || new Error('Status code not 20x') })
+ } else {
+ good_pods.push(pod._id)
+ }
+
+ return callback_each_pod_finished()
+ }
+
+ function callbackAllPodsFinished (err) {
+ if (err) return callback(err)
+
+ updatePodsScore(good_pods, bad_pods)
+ callback(null)
+ }
+ })
+}
+
+function makePoolRequests () {
+ logger.info('Making pool requests to friends.')
+
+ PoolRequests.list(function (err, pool_requests) {
+ if (err) {
+ logger.error('Cannot get the list of pool requests.', { err: err })
+ return // Abort
+ }
+
+ if (pool_requests.length === 0) return
+
+ var requests_to_make = {
+ add: {
+ ids: [],
+ requests: []
+ },
+ remove: {
+ ids: [],
+ requests: []
+ }
+ }
+
+ async.each(pool_requests, function (pool_request, callback_each) {
+ if (pool_request.type === 'add') {
+ requests_to_make.add.requests.push(pool_request.request)
+ requests_to_make.add.ids.push(pool_request._id)
+ } else if (pool_request.type === 'remove') {
+ requests_to_make.remove.requests.push(pool_request.request)
+ requests_to_make.remove.ids.push(pool_request._id)
+ } else {
+ logger.error('Unkown pool request type.', { request_type: pool_request.type })
+ return // abort
+ }
+
+ callback_each()
+ }, function () {
+ // Send the add requests
+ if (requests_to_make.add.requests.length !== 0) {
+ makePoolRequest('add', requests_to_make.add.requests, function (err) {
+ if (err) logger.error('Errors when sent add pool requests.', { error: err })
+
+ PoolRequests.removeRequests(requests_to_make.add.ids)
+ })
+ }
+
+ // Send the remove requests
+ if (requests_to_make.remove.requests.length !== 0) {
+ makePoolRequest('remove', requests_to_make.remove.requests, function (err) {
+ if (err) logger.error('Errors when sent remove pool requests.', { error: err })
+
+ PoolRequests.removeRequests(requests_to_make.remove.ids)
+ })
+ }
+ })
+ })
+}
+
+function removeBadPods () {
+ Pods.findBadPods(function (err, pods) {
+ if (err) {
+ logger.error('Cannot find bad pods.', { error: err })
+ return // abort
+ }
+
+ if (pods.length === 0) return
+
+ var urls = pluck(pods, 'url')
+ var ids = pluck(pods, '_id')
+
+ Videos.removeAllRemotesOf(urls, function (err, r) {
+ if (err) {
+ logger.error('Cannot remove videos from a pod that we removing.', { error: err })
+ } else {
+ var videos_removed = r.result.n
+ logger.info('Removed %d videos.', videos_removed)
+ }
+
+ Pods.removeAllByIds(ids, function (err, r) {
+ if (err) {
+ logger.error('Cannot remove bad pods.', { error: err })
+ } else {
+ var pods_removed = r.result.n
+ logger.info('Removed %d pods.', pods_removed)
+ }
+ })
+ })
+ })
+}
+
+function updatePodsScore (good_pods, bad_pods) {
+ logger.info('Updating %d good pods and %d bad pods scores.', good_pods.length, bad_pods.length)
+
+ Pods.incrementScores(good_pods, constants.PODS_SCORE.BONUS, function (err) {
+ if (err) logger.error('Cannot increment scores of good pods.')
+ })
+
+ Pods.incrementScores(bad_pods, constants.PODS_SCORE.MALUS, function (err) {
+ if (err) logger.error('Cannot increment scores of bad pods.')
+ removeBadPods()
+ })
+}
--- /dev/null
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var path = require('path')
+var webtorrent = require('../lib/webtorrent')
+
+var logger = require('../helpers/logger')
+var Videos = require('../models/videos')
+
+var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
+
+var videos = {
+ seed: seed,
+ seedAllExisting: seedAllExisting
+}
+
+function seed (path, callback) {
+ logger.info('Seeding %s...', path)
+
+ webtorrent.seed(path, function (torrent) {
+ logger.info('%s seeded (%s).', path, torrent.magnetURI)
+
+ return callback(null, torrent)
+ })
+}
+
+function seedAllExisting (callback) {
+ Videos.listOwned(function (err, videos_list) {
+ if (err) {
+ logger.error('Cannot get list of the videos to seed.')
+ return callback(err)
+ }
+
+ async.each(videos_list, function (video, each_callback) {
+ seed(uploadDir + video.namePath, function (err) {
+ if (err) {
+ logger.error('Cannot seed this video.')
+ return callback(err)
+ }
+
+ each_callback(null)
+ })
+ }, callback)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = videos
--- /dev/null
+'use strict'
+
+var config = require('config')
+var ipc = require('node-ipc')
+var pathUtils = require('path')
+var spawn = require('electron-spawn')
+
+var logger = require('../helpers/logger')
+
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+var nodeKey = 'webtorrentnode' + port
+var processKey = 'webtorrentprocess' + port
+ipc.config.silent = true
+ipc.config.id = nodeKey
+
+var webtorrent = {
+ add: add,
+ app: null, // Pid of the app
+ create: create,
+ remove: remove,
+ seed: seed,
+ silent: false // Useful for beautiful tests
+}
+
+function create (options, callback) {
+ if (typeof options === 'function') {
+ callback = options
+ options = {}
+ }
+
+ // Override options
+ if (options.host) host = options.host
+ if (options.port) {
+ port = options.port
+ nodeKey = 'webtorrentnode' + port
+ processKey = 'webtorrentprocess' + port
+ ipc.config.id = nodeKey
+ }
+
+ ipc.serve(function () {
+ if (!webtorrent.silent) logger.info('IPC server ready.')
+
+ // Run a timeout of 30s after which we exit the process
+ var timeout_webtorrent_process = setTimeout(function () {
+ throw new Error('Timeout : cannot run the webtorrent process. Please ensure you have electron-prebuilt npm package installed with xvfb-run.')
+ }, 30000)
+
+ ipc.server.on(processKey + '.ready', function () {
+ if (!webtorrent.silent) logger.info('Webtorrent process ready.')
+ clearTimeout(timeout_webtorrent_process)
+ callback()
+ })
+
+ ipc.server.on(processKey + '.exception', function (data) {
+ throw new Error('Received exception error from webtorrent process.' + data.exception)
+ })
+
+ var webtorrent_process = spawn(pathUtils.join(__dirname, 'webtorrentProcess.js'), host, port, { detached: true })
+ webtorrent_process.stderr.on('data', function (data) {
+ // logger.debug('Webtorrent process stderr: ', data.toString())
+ })
+
+ webtorrent_process.stdout.on('data', function (data) {
+ // logger.debug('Webtorrent process:', data.toString())
+ })
+
+ webtorrent.app = webtorrent_process
+ })
+
+ ipc.server.start()
+}
+
+function seed (path, callback) {
+ var extension = pathUtils.extname(path)
+ var basename = pathUtils.basename(path, extension)
+ var data = {
+ _id: basename,
+ args: {
+ path: path
+ }
+ }
+
+ if (!webtorrent.silent) logger.debug('Node wants to seed %s.', data._id)
+
+ // Finish signal
+ var event_key = nodeKey + '.seedDone.' + data._id
+ ipc.server.on(event_key, function listener (received) {
+ if (!webtorrent.silent) logger.debug('Process seeded torrent %s.', received.magnetUri)
+
+ // This is a fake object, we just use the magnetUri in this project
+ var torrent = {
+ magnetURI: received.magnetUri
+ }
+
+ ipc.server.off(event_key)
+ callback(torrent)
+ })
+
+ ipc.server.broadcast(processKey + '.seed', data)
+}
+
+function add (magnetUri, callback) {
+ var data = {
+ _id: magnetUri,
+ args: {
+ magnetUri: magnetUri
+ }
+ }
+
+ if (!webtorrent.silent) logger.debug('Node wants to add ' + data._id)
+
+ // Finish signal
+ var event_key = nodeKey + '.addDone.' + data._id
+ ipc.server.on(event_key, function (received) {
+ if (!webtorrent.silent) logger.debug('Process added torrent.')
+
+ // This is a fake object, we just use the magnetUri in this project
+ var torrent = {
+ files: received.files
+ }
+
+ ipc.server.off(event_key)
+ callback(torrent)
+ })
+
+ ipc.server.broadcast(processKey + '.add', data)
+}
+
+function remove (magnetUri, callback) {
+ var data = {
+ _id: magnetUri,
+ args: {
+ magnetUri: magnetUri
+ }
+ }
+
+ if (!webtorrent.silent) logger.debug('Node wants to stop seeding %s.', data._id)
+
+ // Finish signal
+ var event_key = nodeKey + '.removeDone.' + data._id
+ ipc.server.on(event_key, function (received) {
+ if (!webtorrent.silent) logger.debug('Process removed torrent %s.', data._id)
+
+ var err = null
+ if (received.err) err = received.err
+
+ ipc.server.off(event_key)
+ callback(err)
+ })
+
+ ipc.server.broadcast(processKey + '.remove', data)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = webtorrent
--- /dev/null
+'use strict'
+
+var WebTorrent = require('webtorrent')
+var ipc = require('node-ipc')
+
+function webtorrent (args) {
+ if (args.length !== 3) {
+ throw new Error('Wrong arguments number: ' + args.length + '/3')
+ }
+
+ var host = args[1]
+ var port = args[2]
+ var nodeKey = 'webtorrentnode' + port
+ var processKey = 'webtorrentprocess' + port
+
+ ipc.config.silent = true
+ ipc.config.id = processKey
+
+ if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = []
+ else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket'
+ var wt = new WebTorrent({ dht: false })
+
+ function seed (data) {
+ var args = data.args
+ var path = args.path
+ var _id = data._id
+
+ wt.seed(path, { announceList: '' }, function (torrent) {
+ var to_send = {
+ magnetUri: torrent.magnetURI
+ }
+
+ ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send)
+ })
+ }
+
+ function add (data) {
+ var args = data.args
+ var magnetUri = args.magnetUri
+ var _id = data._id
+
+ wt.add(magnetUri, function (torrent) {
+ var to_send = {
+ files: []
+ }
+
+ torrent.files.forEach(function (file) {
+ to_send.files.push({ path: file.path })
+ })
+
+ ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send)
+ })
+ }
+
+ function remove (data) {
+ var args = data.args
+ var magnetUri = args.magnetUri
+ var _id = data._id
+
+ try {
+ wt.remove(magnetUri, callback)
+ } catch (err) {
+ console.log('Cannot remove the torrent from WebTorrent.')
+ return callback(null)
+ }
+
+ function callback () {
+ var to_send = {}
+ ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send)
+ }
+ }
+
+ console.log('Configuration: ' + host + ':' + port)
+ console.log('Connecting to IPC...')
+
+ ipc.connectTo(nodeKey, function () {
+ ipc.of[nodeKey].on(processKey + '.seed', seed)
+ ipc.of[nodeKey].on(processKey + '.add', add)
+ ipc.of[nodeKey].on(processKey + '.remove', remove)
+
+ ipc.of[nodeKey].emit(processKey + '.ready')
+ console.log('Ready.')
+ })
+
+ process.on('uncaughtException', function (e) {
+ ipc.of[nodeKey].emit(processKey + '.exception', { exception: e })
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = webtorrent
--- /dev/null
+'use strict'
+
+var cacheMiddleware = {
+ cache: cache
+}
+
+function cache (cache) {
+ return function (req, res, next) {
+ // If we want explicitly a cache
+ // Or if we don't specify if we want a cache or no and we are in production
+ if (cache === true || (cache !== false && process.env.NODE_ENV === 'production')) {
+ res.setHeader('Cache-Control', 'public')
+ } else {
+ res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
+ }
+
+ next()
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = cacheMiddleware
--- /dev/null
+'use strict'
+
+var cacheMiddleware = require('./cache')
+var reqValidatorsMiddleware = require('./reqValidators')
+var secureMiddleware = require('./secure')
+
+var middlewares = {
+ cache: cacheMiddleware,
+ reqValidators: reqValidatorsMiddleware,
+ secure: secureMiddleware
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = middlewares
--- /dev/null
+'use strict'
+
+var podsReqValidators = require('./pods')
+var remoteReqValidators = require('./remote')
+var videosReqValidators = require('./videos')
+
+var reqValidators = {
+ pods: podsReqValidators,
+ remote: remoteReqValidators,
+ videos: videosReqValidators
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidators
--- /dev/null
+'use strict'
+
+var checkErrors = require('./utils').checkErrors
+var friends = require('../../lib/friends')
+var logger = require('../../helpers/logger')
+
+var reqValidatorsPod = {
+ makeFriends: makeFriends,
+ podsAdd: podsAdd
+}
+
+function makeFriends (req, res, next) {
+ friends.hasFriends(function (err, has_friends) {
+ if (err) {
+ logger.error('Cannot know if we have friends.', { error: err })
+ res.sendStatus(500)
+ }
+
+ if (has_friends === true) {
+ // We need to quit our friends before make new ones
+ res.sendStatus(409)
+ } else {
+ return next()
+ }
+ })
+}
+
+function podsAdd (req, res, next) {
+ req.checkBody('data.url', 'Should have an url').notEmpty().isURL({ require_protocol: true })
+ req.checkBody('data.publicKey', 'Should have a public key').notEmpty()
+
+ logger.debug('Checking podsAdd parameters', { parameters: req.body })
+
+ checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsPod
--- /dev/null
+'use strict'
+
+var checkErrors = require('./utils').checkErrors
+var logger = require('../../helpers/logger')
+
+var reqValidatorsRemote = {
+ remoteVideosAdd: remoteVideosAdd,
+ remoteVideosRemove: remoteVideosRemove,
+ secureRequest: secureRequest
+}
+
+function remoteVideosAdd (req, res, next) {
+ req.checkBody('data').isArray()
+ req.checkBody('data').eachIsRemoteVideosAddValid()
+
+ logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body })
+
+ checkErrors(req, res, next)
+}
+
+function remoteVideosRemove (req, res, next) {
+ req.checkBody('data').isArray()
+ req.checkBody('data').eachIsRemoteVideosRemoveValid()
+
+ logger.debug('Checking remoteVideosRemove parameters', { parameters: req.body })
+
+ checkErrors(req, res, next)
+}
+
+function secureRequest (req, res, next) {
+ req.checkBody('signature.url', 'Should have a signature url').isURL()
+ req.checkBody('signature.signature', 'Should have a signature').notEmpty()
+ req.checkBody('key', 'Should have a key').notEmpty()
+ req.checkBody('data', 'Should have data').notEmpty()
+
+ logger.debug('Checking secureRequest parameters', { parameters: { data: req.body.data, keyLength: req.body.key.length } })
+
+ checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsRemote
--- /dev/null
+'use strict'
+
+var util = require('util')
+
+var logger = require('../../helpers/logger')
+
+var reqValidatorsUtils = {
+ checkErrors: checkErrors
+}
+
+function checkErrors (req, res, next, status_code) {
+ if (status_code === undefined) status_code = 400
+ var errors = req.validationErrors()
+
+ if (errors) {
+ logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors })
+ return res.status(status_code).send('There have been validation errors: ' + util.inspect(errors))
+ }
+
+ return next()
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsUtils
--- /dev/null
+'use strict'
+
+var checkErrors = require('./utils').checkErrors
+var logger = require('../../helpers/logger')
+var Videos = require('../../models/videos')
+
+var reqValidatorsVideos = {
+ videosAdd: videosAdd,
+ videosGet: videosGet,
+ videosRemove: videosRemove,
+ videosSearch: videosSearch
+}
+
+function videosAdd (req, res, next) {
+ req.checkFiles('input_video[0].originalname', 'Should have an input video').notEmpty()
+ req.checkFiles('input_video[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i)
+ req.checkBody('name', 'Should have a name').isLength(1, 50)
+ req.checkBody('description', 'Should have a description').isLength(1, 250)
+
+ logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
+
+ checkErrors(req, res, next)
+}
+
+function videosGet (req, res, next) {
+ req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+
+ logger.debug('Checking videosGet parameters', { parameters: req.params })
+
+ checkErrors(req, res, function () {
+ Videos.getVideoState(req.params.id, function (err, state) {
+ if (err) {
+ logger.error('Error in videosGet request validator.', { error: err })
+ res.sendStatus(500)
+ }
+
+ if (state.exist === false) return res.status(404).send('Video not found')
+
+ next()
+ })
+ })
+}
+
+function videosRemove (req, res, next) {
+ req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+
+ logger.debug('Checking videosRemove parameters', { parameters: req.params })
+
+ checkErrors(req, res, function () {
+ Videos.getVideoState(req.params.id, function (err, state) {
+ if (err) {
+ logger.error('Error in videosRemove request validator.', { error: err })
+ res.sendStatus(500)
+ }
+
+ if (state.exist === false) return res.status(404).send('Video not found')
+ else if (state.owned === false) return res.status(403).send('Cannot remove video of another pod')
+
+ next()
+ })
+ })
+}
+
+function videosSearch (req, res, next) {
+ req.checkParams('name', 'Should have a name').notEmpty()
+
+ logger.debug('Checking videosSearch parameters', { parameters: req.params })
+
+ checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsVideos
--- /dev/null
+'use strict'
+
+var logger = require('../helpers/logger')
+var peertubeCrypto = require('../helpers/peertubeCrypto')
+var Pods = require('../models/pods')
+
+var secureMiddleware = {
+ decryptBody: decryptBody
+}
+
+function decryptBody (req, res, next) {
+ var url = req.body.signature.url
+ Pods.findByUrl(url, function (err, pod) {
+ if (err) {
+ logger.error('Cannot get signed url in decryptBody.', { error: err })
+ return res.sendStatus(500)
+ }
+
+ if (pod === null) {
+ logger.error('Unknown pod %s.', url)
+ return res.sendStatus(403)
+ }
+
+ logger.debug('Decrypting body from %s.', url)
+
+ var signature_ok = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature)
+
+ if (signature_ok === true) {
+ peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) {
+ if (err) {
+ logger.error('Cannot decrypt data.', { error: err })
+ return res.sendStatus(500)
+ }
+
+ req.body.data = JSON.parse(decrypted)
+ delete req.body.key
+
+ next()
+ })
+ } else {
+ logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url)
+ return res.sendStatus(403)
+ }
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = secureMiddleware
--- /dev/null
+'use strict'
+
+var mongoose = require('mongoose')
+
+var constants = require('../initializers/constants')
+var logger = require('../helpers/logger')
+
+// ---------------------------------------------------------------------------
+
+var podsSchema = mongoose.Schema({
+ url: String,
+ publicKey: String,
+ score: { type: Number, max: constants.FRIEND_BASE_SCORE }
+})
+var PodsDB = mongoose.model('pods', podsSchema)
+
+// ---------------------------------------------------------------------------
+
+var Pods = {
+ add: add,
+ count: count,
+ findByUrl: findByUrl,
+ findBadPods: findBadPods,
+ incrementScores: incrementScores,
+ list: list,
+ remove: remove,
+ removeAll: removeAll,
+ removeAllByIds: removeAllByIds
+}
+
+// TODO: check if the pod is not already a friend
+function add (data, callback) {
+ if (!callback) callback = function () {}
+ var params = {
+ url: data.url,
+ publicKey: data.publicKey,
+ score: constants.FRIEND_BASE_SCORE
+ }
+
+ PodsDB.create(params, callback)
+}
+
+function count (callback) {
+ return PodsDB.count(callback)
+}
+
+function findBadPods (callback) {
+ PodsDB.find({ score: 0 }, callback)
+}
+
+function findByUrl (url, callback) {
+ PodsDB.findOne({ url: url }, callback)
+}
+
+function incrementScores (ids, value, callback) {
+ if (!callback) callback = function () {}
+ PodsDB.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback)
+}
+
+function list (callback) {
+ PodsDB.find(function (err, pods_list) {
+ if (err) {
+ logger.error('Cannot get the list of the pods.')
+ return callback(err)
+ }
+
+ return callback(null, pods_list)
+ })
+}
+
+function remove (url, callback) {
+ if (!callback) callback = function () {}
+ PodsDB.remove({ url: url }, callback)
+}
+
+function removeAll (callback) {
+ if (!callback) callback = function () {}
+ PodsDB.remove(callback)
+}
+
+function removeAllByIds (ids, callback) {
+ if (!callback) callback = function () {}
+ PodsDB.remove({ _id: { $in: ids } }, callback)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = Pods
--- /dev/null
+'use strict'
+
+var mongoose = require('mongoose')
+
+var logger = require('../helpers/logger')
+
+// ---------------------------------------------------------------------------
+
+var poolRequestsSchema = mongoose.Schema({
+ type: String,
+ id: String, // Special id to find duplicates (video created we want to remove...)
+ request: mongoose.Schema.Types.Mixed
+})
+var PoolRequestsDB = mongoose.model('poolRequests', poolRequestsSchema)
+
+// ---------------------------------------------------------------------------
+
+var PoolRequests = {
+ create: create,
+ findById: findById,
+ list: list,
+ removeRequestById: removeRequestById,
+ removeRequests: removeRequests
+}
+
+function create (id, type, request, callback) {
+ PoolRequestsDB.create({ id: id, type: type, request: request }, callback)
+}
+
+function findById (id, callback) {
+ PoolRequestsDB.findOne({ id: id }, callback)
+}
+
+function list (callback) {
+ PoolRequestsDB.find({}, { _id: 1, type: 1, request: 1 }, callback)
+}
+
+function removeRequestById (id, callback) {
+ PoolRequestsDB.remove({ id: id }, callback)
+}
+
+function removeRequests (ids) {
+ PoolRequestsDB.remove({ _id: { $in: ids } }, function (err) {
+ if (err) {
+ logger.error('Cannot remove requests from the pool requests database.', { error: err })
+ return // Abort
+ }
+
+ logger.info('Pool requests flushed.')
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = PoolRequests
--- /dev/null
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var dz = require('dezalgo')
+var fs = require('fs')
+var mongoose = require('mongoose')
+var path = require('path')
+
+var logger = require('../helpers/logger')
+
+var http = config.get('webserver.https') === true ? 'https' : 'http'
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
+
+// ---------------------------------------------------------------------------
+
+var videosSchema = mongoose.Schema({
+ name: String,
+ namePath: String,
+ description: String,
+ magnetUri: String,
+ podUrl: String
+})
+var VideosDB = mongoose.model('videos', videosSchema)
+
+// ---------------------------------------------------------------------------
+
+var Videos = {
+ add: add,
+ addRemotes: addRemotes,
+ get: get,
+ getVideoState: getVideoState,
+ isOwned: isOwned,
+ list: list,
+ listOwned: listOwned,
+ removeOwned: removeOwned,
+ removeAllRemotes: removeAllRemotes,
+ removeAllRemotesOf: removeAllRemotesOf,
+ removeRemotesOfByMagnetUris: removeRemotesOfByMagnetUris,
+ search: search
+}
+
+function add (video, callback) {
+ logger.info('Adding %s video to database.', video.name)
+
+ var params = video
+ params.podUrl = http + '://' + host + ':' + port
+
+ VideosDB.create(params, function (err, video) {
+ if (err) {
+ logger.error('Cannot insert this video into database.')
+ return callback(err)
+ }
+
+ callback(null)
+ })
+}
+
+// TODO: avoid doublons
+function addRemotes (videos, callback) {
+ if (!callback) callback = function () {}
+
+ var to_add = []
+
+ async.each(videos, function (video, callback_each) {
+ callback_each = dz(callback_each)
+ logger.debug('Add remote video from pod: %s', video.podUrl)
+
+ var params = {
+ name: video.name,
+ namePath: null,
+ description: video.description,
+ magnetUri: video.magnetUri,
+ podUrl: video.podUrl
+ }
+
+ to_add.push(params)
+
+ callback_each()
+ }, function () {
+ VideosDB.create(to_add, function (err, videos) {
+ if (err) {
+ logger.error('Cannot insert this remote video.')
+ return callback(err)
+ }
+
+ return callback(null, videos)
+ })
+ })
+}
+
+function get (id, callback) {
+ VideosDB.findById(id, function (err, video) {
+ if (err) {
+ logger.error('Cannot get this video.')
+ return callback(err)
+ }
+
+ return callback(null, video)
+ })
+}
+
+function getVideoState (id, callback) {
+ get(id, function (err, video) {
+ if (err) return callback(err)
+
+ var exist = (video !== null)
+ var owned = false
+ if (exist === true) {
+ owned = (video.namePath !== null)
+ }
+
+ return callback(null, { exist: exist, owned: owned })
+ })
+}
+
+function isOwned (id, callback) {
+ VideosDB.findById(id, function (err, video) {
+ if (err || !video) {
+ if (!err) err = new Error('Cannot find this video.')
+ logger.error('Cannot find this video.')
+ return callback(err)
+ }
+
+ if (video.namePath === null) {
+ var error_string = 'Cannot remove the video of another pod.'
+ logger.error(error_string)
+ return callback(new Error(error_string), false, video)
+ }
+
+ callback(null, true, video)
+ })
+}
+
+function list (callback) {
+ VideosDB.find(function (err, videos_list) {
+ if (err) {
+ logger.error('Cannot get the list of the videos.')
+ return callback(err)
+ }
+
+ return callback(null, videos_list)
+ })
+}
+
+function listOwned (callback) {
+ // If namePath is not null this is *our* video
+ VideosDB.find({ namePath: { $ne: null } }, function (err, videos_list) {
+ if (err) {
+ logger.error('Cannot get the list of owned videos.')
+ return callback(err)
+ }
+
+ return callback(null, videos_list)
+ })
+}
+
+function removeOwned (id, callback) {
+ VideosDB.findByIdAndRemove(id, function (err, video) {
+ if (err) {
+ logger.error('Cannot remove the torrent.')
+ return callback(err)
+ }
+
+ fs.unlink(uploadDir + video.namePath, function (err) {
+ if (err) {
+ logger.error('Cannot remove this video file.')
+ return callback(err)
+ }
+
+ callback(null)
+ })
+ })
+}
+
+function removeAllRemotes (callback) {
+ VideosDB.remove({ namePath: null }, callback)
+}
+
+function removeAllRemotesOf (fromUrl, callback) {
+ VideosDB.remove({ podUrl: fromUrl }, callback)
+}
+
+// Use the magnet Uri because the _id field is not the same on different servers
+function removeRemotesOfByMagnetUris (fromUrl, magnetUris, callback) {
+ if (callback === undefined) callback = function () {}
+
+ VideosDB.find({ magnetUri: { $in: magnetUris } }, function (err, videos) {
+ if (err || !videos) {
+ logger.error('Cannot find the torrent URI of these remote videos.')
+ return callback(err)
+ }
+
+ var to_remove = []
+ async.each(videos, function (video, callback_async) {
+ callback_async = dz(callback_async)
+
+ if (video.podUrl !== fromUrl) {
+ logger.error('The pod %s has not the rights on the video of %s.', fromUrl, video.podUrl)
+ } else {
+ to_remove.push(video._id)
+ }
+
+ callback_async()
+ }, function () {
+ VideosDB.remove({ _id: { $in: to_remove } }, function (err) {
+ if (err) {
+ logger.error('Cannot remove the remote videos.')
+ return callback(err)
+ }
+
+ logger.info('Removed remote videos from %s.', fromUrl)
+ callback(null)
+ })
+ })
+ })
+}
+
+function search (name, callback) {
+ VideosDB.find({ name: new RegExp(name) }, function (err, videos) {
+ if (err) {
+ logger.error('Cannot search the videos.')
+ return callback(err)
+ }
+
+ return callback(null, videos)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = Videos
--- /dev/null
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var pathUtils = require('path')
+var request = require('supertest')
+
+var utils = require('./utils')
+
+describe('Test parameters validator', function () {
+ var app = null
+ var url = ''
+
+ function makePostRequest (path, fields, attach, done, fail) {
+ var status_code = 400
+ if (fail !== undefined && fail === false) status_code = 200
+
+ var req = request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+
+ Object.keys(fields).forEach(function (field) {
+ var value = fields[field]
+ req.field(field, value)
+ })
+
+ req.expect(status_code, done)
+ }
+
+ function makePostBodyRequest (path, fields, done, fail) {
+ var status_code = 400
+ if (fail !== undefined && fail === false) status_code = 200
+
+ request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+ .send(fields)
+ .expect(status_code, done)
+ }
+
+ // ---------------------------------------------------------------
+
+ before(function (done) {
+ this.timeout(20000)
+
+ async.series([
+ function (next) {
+ utils.flushTests(next)
+ },
+ function (next) {
+ utils.runServer(1, function (app1, url1) {
+ app = app1
+ url = url1
+ next()
+ })
+ }
+ ], done)
+ })
+
+ describe('Of the pods API', function () {
+ var path = '/api/v1/pods/'
+
+ describe('When adding a pod', function () {
+ it('Should fail with nothing', function (done) {
+ var data = {}
+ makePostBodyRequest(path, data, done)
+ })
+
+ it('Should fail without public key', function (done) {
+ var data = {
+ data: {
+ url: 'http://coucou.com'
+ }
+ }
+ makePostBodyRequest(path, data, done)
+ })
+
+ it('Should fail without an url', function (done) {
+ var data = {
+ data: {
+ publicKey: 'mysuperpublickey'
+ }
+ }
+ makePostBodyRequest(path, data, done)
+ })
+
+ it('Should fail with an incorrect url', function (done) {
+ var data = {
+ data: {
+ url: 'coucou.com',
+ publicKey: 'mysuperpublickey'
+ }
+ }
+ makePostBodyRequest(path, data, function () {
+ data.data.url = 'http://coucou'
+ makePostBodyRequest(path, data, function () {
+ data.data.url = 'coucou'
+ makePostBodyRequest(path, data, done)
+ })
+ })
+ })
+
+ it('Should succeed with the correct parameters', function (done) {
+ var data = {
+ data: {
+ url: 'http://coucou.com',
+ publicKey: 'mysuperpublickey'
+ }
+ }
+ makePostBodyRequest(path, data, done, false)
+ })
+ })
+ })
+
+ describe('Of the videos API', function () {
+ var path = '/api/v1/videos/'
+
+ describe('When searching a video', function () {
+ it('Should fail with nothing', function (done) {
+ request(url)
+ .get(pathUtils.join(path, 'search'))
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+ })
+
+ describe('When adding a video', function () {
+ it('Should fail with nothing', function (done) {
+ var data = {}
+ var attach = {}
+ makePostRequest(path, data, attach, done)
+ })
+
+ it('Should fail without name', function (done) {
+ var data = {
+ description: 'my super description'
+ }
+ var attach = {
+ 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ makePostRequest(path, data, attach, done)
+ })
+
+ it('Should fail with a long name', function (done) {
+ var data = {
+ name: 'My very very very very very very very very very very very very very very very very long name',
+ description: 'my super description'
+ }
+ var attach = {
+ 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ makePostRequest(path, data, attach, done)
+ })
+
+ it('Should fail without description', function (done) {
+ var data = {
+ name: 'my super name'
+ }
+ var attach = {
+ 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ makePostRequest(path, data, attach, done)
+ })
+
+ it('Should fail with a long description', function (done) {
+ var data = {
+ name: 'my super name',
+ description: 'my super description which is very very very very very very very very very very very very very very' +
+ 'very very very very very very very very very very very very very very very very very very very very very' +
+ 'very very very very very very very very very very very very very very very long'
+ }
+ var attach = {
+ 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ makePostRequest(path, data, attach, done)
+ })
+
+ it('Should fail without an input file', function (done) {
+ var data = {
+ name: 'my super name',
+ description: 'my super description'
+ }
+ var attach = {}
+ makePostRequest(path, data, attach, done)
+ })
+
+ it('Should fail without an incorrect input file', function (done) {
+ var data = {
+ name: 'my super name',
+ description: 'my super description'
+ }
+ var attach = {
+ 'input_video': pathUtils.join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+ }
+ makePostRequest(path, data, attach, done)
+ })
+
+ it('Should succeed with the correct parameters', function (done) {
+ var data = {
+ name: 'my super name',
+ description: 'my super description'
+ }
+ var attach = {
+ 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ makePostRequest(path, data, attach, function () {
+ attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
+ makePostRequest(path, data, attach, function () {
+ attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
+ makePostRequest(path, data, attach, done, true)
+ }, true)
+ }, true)
+ })
+ })
+
+ describe('When getting a video', function () {
+ it('Should return the list of the videos with nothing', function (done) {
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(0)
+
+ done()
+ })
+ })
+
+ it('Should fail without a mongodb id', function (done) {
+ request(url)
+ .get(path + 'coucou')
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should return 404 with an incorrect video', function (done) {
+ request(url)
+ .get(path + '123456789012345678901234')
+ .set('Accept', 'application/json')
+ .expect(404, done)
+ })
+
+ it('Should succeed with the correct parameters')
+ })
+
+ describe('When removing a video', function () {
+ it('Should have 404 with nothing', function (done) {
+ request(url)
+ .delete(path)
+ .expect(404, done)
+ })
+
+ it('Should fail without a mongodb id', function (done) {
+ request(url)
+ .delete(path + 'hello')
+ .expect(400, done)
+ })
+
+ it('Should fail with a video which does not exist', function (done) {
+ request(url)
+ .delete(path + '123456789012345678901234')
+ .expect(404, done)
+ })
+
+ it('Should fail with a video of another pod')
+
+ it('Should succeed with the correct parameters')
+ })
+ })
+
+ describe('Of the remote videos API', function () {
+ describe('When making a secure request', function () {
+ it('Should check a secure request')
+ })
+
+ describe('When adding a video', function () {
+ it('Should check when adding a video')
+ })
+
+ describe('When removing a video', function () {
+ it('Should check when removing a video')
+ })
+ })
+
+ after(function (done) {
+ process.kill(-app.pid)
+
+ // Keep the logs if the test failed
+ if (this.ok) {
+ utils.flushTests(done)
+ } else {
+ done()
+ }
+ })
+})
--- /dev/null
+this is a fake video mouahahah
--- /dev/null
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+
+var utils = require('./utils')
+
+describe('Test advanced friends', function () {
+ var apps = []
+ var urls = []
+
+ function makeFriends (pod_number, callback) {
+ return utils.makeFriends(urls[pod_number - 1], callback)
+ }
+
+ function quitFriends (pod_number, callback) {
+ return utils.quitFriends(urls[pod_number - 1], callback)
+ }
+
+ function getFriendsList (pod_number, end) {
+ return utils.getFriendsList(urls[pod_number - 1], end)
+ }
+
+ function uploadVideo (pod_number, callback) {
+ var name = 'my super video'
+ var description = 'my super description'
+ var fixture = 'video_short.webm'
+
+ return utils.uploadVideo(urls[pod_number - 1], name, description, fixture, callback)
+ }
+
+ function getVideos (pod_number, callback) {
+ return utils.getVideosList(urls[pod_number - 1], callback)
+ }
+
+ // ---------------------------------------------------------------
+
+ before(function (done) {
+ this.timeout(30000)
+ utils.flushAndRunMultipleServers(6, function (apps_run, urls_run) {
+ apps = apps_run
+ urls = urls_run
+ done()
+ })
+ })
+
+ it('Should make friends with two pod each in a different group', function (done) {
+ this.timeout(20000)
+
+ async.series([
+ // Pod 3 makes friend with the first one
+ function (next) {
+ makeFriends(3, next)
+ },
+ // Pod 4 makes friend with the second one
+ function (next) {
+ makeFriends(4, next)
+ },
+ // Now if the fifth wants to make friends with the third et the first
+ function (next) {
+ makeFriends(5, next)
+ },
+ function (next) {
+ setTimeout(next, 11000)
+ }],
+ function (err) {
+ if (err) throw err
+
+ // It should have 0 friends
+ getFriendsList(5, function (err, res) {
+ if (err) throw err
+
+ expect(res.body.length).to.equal(0)
+
+ done()
+ })
+ }
+ )
+ })
+
+ it('Should quit all friends', function (done) {
+ this.timeout(10000)
+
+ async.series([
+ function (next) {
+ quitFriends(1, next)
+ },
+ function (next) {
+ quitFriends(2, next)
+ }],
+ function (err) {
+ if (err) throw err
+
+ async.each([ 1, 2, 3, 4, 5, 6 ], function (i, callback) {
+ getFriendsList(i, function (err, res) {
+ if (err) throw err
+
+ expect(res.body.length).to.equal(0)
+
+ callback()
+ })
+ }, done)
+ }
+ )
+ })
+
+ it('Should make friends with the pods 1, 2, 3', function (done) {
+ this.timeout(150000)
+
+ async.series([
+ // Pods 1, 2, 3 and 4 become friends
+ function (next) {
+ makeFriends(2, next)
+ },
+ function (next) {
+ makeFriends(1, next)
+ },
+ function (next) {
+ makeFriends(4, next)
+ },
+ // Kill pod 4
+ function (next) {
+ apps[3].kill()
+ next()
+ },
+ // Expulse pod 4 from pod 1 and 2
+ function (next) {
+ uploadVideo(1, next)
+ },
+ function (next) {
+ uploadVideo(2, next)
+ },
+ function (next) {
+ setTimeout(next, 11000)
+ },
+ function (next) {
+ uploadVideo(1, next)
+ },
+ function (next) {
+ uploadVideo(2, next)
+ },
+ function (next) {
+ setTimeout(next, 20000)
+ },
+ // Rerun server 4
+ function (next) {
+ utils.runServer(4, function (app, url) {
+ apps[3] = app
+ next()
+ })
+ },
+ function (next) {
+ getFriendsList(4, function (err, res) {
+ if (err) throw err
+
+ // Pod 4 didn't know pod 1 and 2 removed it
+ expect(res.body.length).to.equal(3)
+
+ next()
+ })
+ },
+ // Pod 6 ask pod 1, 2 and 3
+ function (next) {
+ makeFriends(6, next)
+ }],
+ function (err) {
+ if (err) throw err
+
+ getFriendsList(6, function (err, res) {
+ if (err) throw err
+
+ // Pod 4 should not be our friend
+ var result = res.body
+ expect(result.length).to.equal(3)
+ for (var pod of result) {
+ expect(pod.url).not.equal(urls[3])
+ }
+
+ done()
+ })
+ }
+ )
+ })
+
+ it('Should pod 1 quit friends', function (done) {
+ this.timeout(25000)
+
+ async.series([
+ // Upload a video on server 3 for aditionnal tests
+ function (next) {
+ uploadVideo(3, next)
+ },
+ function (next) {
+ setTimeout(next, 15000)
+ },
+ function (next) {
+ quitFriends(1, next)
+ },
+ // Remove pod 1 from pod 2
+ function (next) {
+ getVideos(1, function (err, res) {
+ if (err) throw err
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(2)
+
+ next()
+ })
+ }],
+ function (err) {
+ if (err) throw err
+
+ getVideos(2, function (err, res) {
+ if (err) throw err
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(3)
+ done()
+ })
+ }
+ )
+ })
+
+ it('Should make friends between pod 1 and 2 and exchange their videos', function (done) {
+ this.timeout(20000)
+ makeFriends(1, function () {
+ setTimeout(function () {
+ getVideos(1, function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(5)
+
+ done()
+ })
+ }, 5000)
+ })
+ })
+
+ after(function (done) {
+ apps.forEach(function (app) {
+ process.kill(-app.pid)
+ })
+
+ if (this.ok) {
+ utils.flushTests(done)
+ } else {
+ done()
+ }
+ })
+})
--- /dev/null
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var request = require('supertest')
+
+var utils = require('./utils')
+
+describe('Test basic friends', function () {
+ var apps = []
+ var urls = []
+
+ function testMadeFriends (urls, url_to_test, callback) {
+ var friends = []
+ for (var i = 0; i < urls.length; i++) {
+ if (urls[i] === url_to_test) continue
+ friends.push(urls[i])
+ }
+
+ utils.getFriendsList(url_to_test, function (err, res) {
+ if (err) throw err
+
+ var result = res.body
+ var result_urls = [ result[0].url, result[1].url ]
+ expect(result).to.be.an('array')
+ expect(result.length).to.equal(2)
+ expect(result_urls[0]).to.not.equal(result_urls[1])
+
+ var error_string = 'Friends url do not correspond for ' + url_to_test
+ expect(friends).to.contain(result_urls[0], error_string)
+ expect(friends).to.contain(result_urls[1], error_string)
+ callback()
+ })
+ }
+
+ // ---------------------------------------------------------------
+
+ before(function (done) {
+ this.timeout(20000)
+ utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
+ apps = apps_run
+ urls = urls_run
+ done()
+ })
+ })
+
+ it('Should not have friends', function (done) {
+ async.each(urls, function (url, callback) {
+ utils.getFriendsList(url, function (err, res) {
+ if (err) throw err
+
+ var result = res.body
+ expect(result).to.be.an('array')
+ expect(result.length).to.equal(0)
+ callback()
+ })
+ }, done)
+ })
+
+ it('Should make friends', function (done) {
+ this.timeout(10000)
+
+ var path = '/api/v1/pods/makefriends'
+
+ async.series([
+ // The second pod make friend with the third
+ function (next) {
+ request(urls[1])
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(204)
+ .end(next)
+ },
+ // Wait for the request between pods
+ function (next) {
+ setTimeout(next, 1000)
+ },
+ // The second pod should have the third as a friend
+ function (next) {
+ utils.getFriendsList(urls[1], function (err, res) {
+ if (err) throw err
+
+ var result = res.body
+ expect(result).to.be.an('array')
+ expect(result.length).to.equal(1)
+ expect(result[0].url).to.be.equal(urls[2])
+
+ next()
+ })
+ },
+ // Same here, the third pod should have the second pod as a friend
+ function (next) {
+ utils.getFriendsList(urls[2], function (err, res) {
+ if (err) throw err
+
+ var result = res.body
+ expect(result).to.be.an('array')
+ expect(result.length).to.equal(1)
+ expect(result[0].url).to.be.equal(urls[1])
+
+ next()
+ })
+ },
+ // Finally the first pod make friend with the second pod
+ function (next) {
+ request(urls[0])
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(204)
+ .end(next)
+ },
+ // Wait for the request between pods
+ function (next) {
+ setTimeout(next, 1000)
+ }
+ ],
+ // Now each pod should be friend with the other ones
+ function (err) {
+ if (err) throw err
+ async.each(urls, function (url, callback) {
+ testMadeFriends(urls, url, callback)
+ }, done)
+ })
+ })
+
+ it('Should not be allowed to make friend again', function (done) {
+ utils.makeFriends(urls[1], 409, done)
+ })
+
+ it('Should quit friends of pod 2', function (done) {
+ async.series([
+ // Pod 1 quit friends
+ function (next) {
+ utils.quitFriends(urls[1], next)
+ },
+ // Pod 1 should not have friends anymore
+ function (next) {
+ utils.getFriendsList(urls[1], function (err, res) {
+ if (err) throw err
+
+ var result = res.body
+ expect(result).to.be.an('array')
+ expect(result.length).to.equal(0)
+
+ next()
+ })
+ },
+ // Other pods shouldn't have pod 1 too
+ function (next) {
+ async.each([ urls[0], urls[2] ], function (url, callback) {
+ utils.getFriendsList(url, function (err, res) {
+ if (err) throw err
+
+ var result = res.body
+ expect(result).to.be.an('array')
+ expect(result.length).to.equal(1)
+ expect(result[0].url).not.to.be.equal(urls[1])
+ callback()
+ })
+ }, next)
+ }
+ ], done)
+ })
+
+ it('Should allow pod 2 to make friend again', function (done) {
+ utils.makeFriends(urls[1], function () {
+ async.each(urls, function (url, callback) {
+ testMadeFriends(urls, url, callback)
+ }, done)
+ })
+ })
+
+ after(function (done) {
+ apps.forEach(function (app) {
+ process.kill(-app.pid)
+ })
+
+ if (this.ok) {
+ utils.flushTests(done)
+ } else {
+ done()
+ }
+ })
+})
--- /dev/null
+'use strict'
+
+// Order of the tests we want to execute
+require('./checkParams')
+require('./friendsBasic')
+require('./singlePod')
+require('./multiplePods')
+require('./friendsAdvanced')
--- /dev/null
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var pathUtils = require('path')
+
+var utils = require('./utils')
+var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
+webtorrent.silent = true
+
+describe('Test multiple pods', function () {
+ var apps = []
+ var urls = []
+ var to_remove = []
+
+ before(function (done) {
+ this.timeout(30000)
+
+ async.series([
+ // Run servers
+ function (next) {
+ utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
+ apps = apps_run
+ urls = urls_run
+ next()
+ })
+ },
+ // The second pod make friend with the third
+ function (next) {
+ utils.makeFriends(urls[1], next)
+ },
+ // Wait for the request between pods
+ function (next) {
+ setTimeout(next, 10000)
+ },
+ // Pod 1 make friends too
+ function (next) {
+ utils.makeFriends(urls[0], next)
+ },
+ function (next) {
+ webtorrent.create({ host: 'client', port: '1' }, next)
+ }
+ ], done)
+ })
+
+ it('Should not have videos for all pods', function (done) {
+ async.each(urls, function (url, callback) {
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(0)
+
+ callback()
+ })
+ }, done)
+ })
+
+ describe('Should upload the video and propagate on each pod', function () {
+ it('Should upload the video on pod 1 and propagate on each pod', function (done) {
+ this.timeout(15000)
+
+ async.series([
+ function (next) {
+ utils.uploadVideo(urls[0], 'my super name for pod 1', 'my super description for pod 1', 'video_short1.webm', next)
+ },
+ function (next) {
+ setTimeout(next, 11000)
+ }],
+ // All pods should have this video
+ function (err) {
+ if (err) throw err
+
+ async.each(urls, function (url, callback) {
+ var base_magnet = null
+
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ var videos = res.body
+ expect(videos).to.be.an('array')
+ expect(videos.length).to.equal(1)
+ var video = videos[0]
+ expect(video.name).to.equal('my super name for pod 1')
+ expect(video.description).to.equal('my super description for pod 1')
+ expect(video.podUrl).to.equal('http://localhost:9001')
+ expect(video.magnetUri).to.exist
+
+ // All pods should have the same magnet Uri
+ if (base_magnet === null) {
+ base_magnet = video.magnetUri
+ } else {
+ expect(video.magnetUri).to.equal.magnetUri
+ }
+
+ callback()
+ })
+ }, done)
+ }
+ )
+ })
+
+ it('Should upload the video on pod 2 and propagate on each pod', function (done) {
+ this.timeout(15000)
+
+ async.series([
+ function (next) {
+ utils.uploadVideo(urls[1], 'my super name for pod 2', 'my super description for pod 2', 'video_short2.webm', next)
+ },
+ function (next) {
+ setTimeout(next, 11000)
+ }],
+ // All pods should have this video
+ function (err) {
+ if (err) throw err
+
+ async.each(urls, function (url, callback) {
+ var base_magnet = null
+
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ var videos = res.body
+ expect(videos).to.be.an('array')
+ expect(videos.length).to.equal(2)
+ var video = videos[1]
+ expect(video.name).to.equal('my super name for pod 2')
+ expect(video.description).to.equal('my super description for pod 2')
+ expect(video.podUrl).to.equal('http://localhost:9002')
+ expect(video.magnetUri).to.exist
+
+ // All pods should have the same magnet Uri
+ if (base_magnet === null) {
+ base_magnet = video.magnetUri
+ } else {
+ expect(video.magnetUri).to.equal.magnetUri
+ }
+
+ callback()
+ })
+ }, done)
+ }
+ )
+ })
+
+ it('Should upload two videos on pod 3 and propagate on each pod', function (done) {
+ this.timeout(30000)
+
+ async.series([
+ function (next) {
+ utils.uploadVideo(urls[2], 'my super name for pod 3', 'my super description for pod 3', 'video_short3.webm', next)
+ },
+ function (next) {
+ utils.uploadVideo(urls[2], 'my super name for pod 3-2', 'my super description for pod 3-2', 'video_short.webm', next)
+ },
+ function (next) {
+ setTimeout(next, 22000)
+ }],
+ function (err) {
+ if (err) throw err
+
+ var base_magnet = null
+ // All pods should have this video
+ async.each(urls, function (url, callback) {
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ var videos = res.body
+ expect(videos).to.be.an('array')
+ expect(videos.length).to.equal(4)
+ var video = videos[2]
+ expect(video.name).to.equal('my super name for pod 3')
+ expect(video.description).to.equal('my super description for pod 3')
+ expect(video.podUrl).to.equal('http://localhost:9003')
+ expect(video.magnetUri).to.exist
+
+ video = videos[3]
+ expect(video.name).to.equal('my super name for pod 3-2')
+ expect(video.description).to.equal('my super description for pod 3-2')
+ expect(video.podUrl).to.equal('http://localhost:9003')
+ expect(video.magnetUri).to.exist
+
+ // All pods should have the same magnet Uri
+ if (base_magnet === null) {
+ base_magnet = video.magnetUri
+ } else {
+ expect(video.magnetUri).to.equal.magnetUri
+ }
+
+ callback()
+ })
+ }, done)
+ }
+ )
+ })
+ })
+
+ describe('Should seed the uploaded video', function () {
+ it('Should add the file 1 by asking pod 3', function (done) {
+ // Yes, this could be long
+ this.timeout(200000)
+
+ utils.getVideosList(urls[2], function (err, res) {
+ if (err) throw err
+
+ var video = res.body[0]
+ to_remove.push(res.body[2]._id)
+ to_remove.push(res.body[3]._id)
+
+ webtorrent.add(video.magnetUri, function (torrent) {
+ expect(torrent.files).to.exist
+ expect(torrent.files.length).to.equal(1)
+ expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+ done()
+ })
+ })
+ })
+
+ it('Should add the file 2 by asking pod 1', function (done) {
+ // Yes, this could be long
+ this.timeout(200000)
+
+ utils.getVideosList(urls[0], function (err, res) {
+ if (err) throw err
+
+ var video = res.body[1]
+
+ webtorrent.add(video.magnetUri, function (torrent) {
+ expect(torrent.files).to.exist
+ expect(torrent.files.length).to.equal(1)
+ expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+ done()
+ })
+ })
+ })
+
+ it('Should add the file 3 by asking pod 2', function (done) {
+ // Yes, this could be long
+ this.timeout(200000)
+
+ utils.getVideosList(urls[1], function (err, res) {
+ if (err) throw err
+
+ var video = res.body[2]
+
+ webtorrent.add(video.magnetUri, function (torrent) {
+ expect(torrent.files).to.exist
+ expect(torrent.files.length).to.equal(1)
+ expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+ done()
+ })
+ })
+ })
+
+ it('Should add the file 3-2 by asking pod 1', function (done) {
+ // Yes, this could be long
+ this.timeout(200000)
+
+ utils.getVideosList(urls[0], function (err, res) {
+ if (err) throw err
+
+ var video = res.body[3]
+
+ webtorrent.add(video.magnetUri, function (torrent) {
+ expect(torrent.files).to.exist
+ expect(torrent.files.length).to.equal(1)
+ expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+ done()
+ })
+ })
+ })
+
+ it('Should remove the file 3 and 3-2 by asking pod 3', function (done) {
+ this.timeout(15000)
+
+ async.series([
+ function (next) {
+ utils.removeVideo(urls[2], to_remove[0], next)
+ },
+ function (next) {
+ utils.removeVideo(urls[2], to_remove[1], next)
+ }],
+ function (err) {
+ if (err) throw err
+ setTimeout(done, 11000)
+ }
+ )
+ })
+
+ it('Should have videos 1 and 3 on each pod', function (done) {
+ async.each(urls, function (url, callback) {
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ var videos = res.body
+ expect(videos).to.be.an('array')
+ expect(videos.length).to.equal(2)
+ expect(videos[0]._id).not.to.equal(videos[1]._id)
+ expect(videos[0]._id).not.to.equal(to_remove[0])
+ expect(videos[1]._id).not.to.equal(to_remove[0])
+ expect(videos[0]._id).not.to.equal(to_remove[1])
+ expect(videos[1]._id).not.to.equal(to_remove[1])
+
+ callback()
+ })
+ }, done)
+ })
+ })
+
+ after(function (done) {
+ apps.forEach(function (app) {
+ process.kill(-app.pid)
+ })
+ process.kill(-webtorrent.app.pid)
+
+ // Keep the logs if the test failed
+ if (this.ok) {
+ utils.flushTests(done)
+ } else {
+ done()
+ }
+ })
+})
--- /dev/null
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var fs = require('fs')
+var pathUtils = require('path')
+
+var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
+webtorrent.silent = true
+
+var utils = require('./utils')
+
+describe('Test a single pod', function () {
+ var app = null
+ var url = ''
+ var video_id = -1
+
+ before(function (done) {
+ this.timeout(20000)
+
+ async.series([
+ function (next) {
+ utils.flushTests(next)
+ },
+ function (next) {
+ utils.runServer(1, function (app1, url1) {
+ app = app1
+ url = url1
+ next()
+ })
+ },
+ function (next) {
+ webtorrent.create({ host: 'client', port: '1' }, next)
+ }
+ ], done)
+ })
+
+ it('Should not have videos', function (done) {
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(0)
+
+ done()
+ })
+ })
+
+ it('Should upload the video', function (done) {
+ this.timeout(5000)
+ utils.uploadVideo(url, 'my super name', 'my super description', 'video_short.webm', done)
+ })
+
+ it('Should seed the uploaded video', function (done) {
+ // Yes, this could be long
+ this.timeout(60000)
+
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(1)
+
+ var video = res.body[0]
+ expect(video.name).to.equal('my super name')
+ expect(video.description).to.equal('my super description')
+ expect(video.podUrl).to.equal('http://localhost:9001')
+ expect(video.magnetUri).to.exist
+
+ video_id = video._id
+
+ webtorrent.add(video.magnetUri, function (torrent) {
+ expect(torrent.files).to.exist
+ expect(torrent.files.length).to.equal(1)
+ expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+ done()
+ })
+ })
+ })
+
+ it('Should search the video', function (done) {
+ utils.searchVideo(url, 'my', function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(1)
+
+ var video = res.body[0]
+ expect(video.name).to.equal('my super name')
+ expect(video.description).to.equal('my super description')
+ expect(video.podUrl).to.equal('http://localhost:9001')
+ expect(video.magnetUri).to.exist
+
+ done()
+ })
+ })
+
+ it('Should not find a search', function (done) {
+ utils.searchVideo(url, 'hello', function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(0)
+
+ done()
+ })
+ })
+
+ it('Should remove the video', function (done) {
+ utils.removeVideo(url, video_id, function (err) {
+ if (err) throw err
+
+ fs.readdir(pathUtils.join(__dirname, '../../test1/uploads/'), function (err, files) {
+ if (err) throw err
+
+ expect(files.length).to.equal(0)
+ done()
+ })
+ })
+ })
+
+ it('Should not have videos', function (done) {
+ utils.getVideosList(url, function (err, res) {
+ if (err) throw err
+
+ expect(res.body).to.be.an('array')
+ expect(res.body.length).to.equal(0)
+
+ done()
+ })
+ })
+
+ after(function (done) {
+ process.kill(-app.pid)
+ process.kill(-webtorrent.app.pid)
+
+ // Keep the logs if the test failed
+ if (this.ok) {
+ utils.flushTests(done)
+ } else {
+ done()
+ }
+ })
+})
--- /dev/null
+'use strict'
+
+var child_process = require('child_process')
+var exec = child_process.exec
+var fork = child_process.fork
+var pathUtils = require('path')
+var request = require('supertest')
+
+var testUtils = {
+ flushTests: flushTests,
+ getFriendsList: getFriendsList,
+ getVideosList: getVideosList,
+ makeFriends: makeFriends,
+ quitFriends: quitFriends,
+ removeVideo: removeVideo,
+ flushAndRunMultipleServers: flushAndRunMultipleServers,
+ runServer: runServer,
+ searchVideo: searchVideo,
+ uploadVideo: uploadVideo
+}
+
+// ---------------------- Export functions --------------------
+
+function flushTests (callback) {
+ exec(pathUtils.join(__dirname, '../../scripts/clean_test.sh'), callback)
+}
+
+function getFriendsList (url, end) {
+ var path = '/api/v1/pods/'
+
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function getVideosList (url, end) {
+ var path = '/api/v1/videos'
+
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function makeFriends (url, expected_status, callback) {
+ if (!callback) {
+ callback = expected_status
+ expected_status = 204
+ }
+
+ var path = '/api/v1/pods/makefriends'
+
+ // The first pod make friend with the third
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(expected_status)
+ .end(function (err, res) {
+ if (err) throw err
+
+ // Wait for the request between pods
+ setTimeout(callback, 1000)
+ })
+}
+
+function quitFriends (url, callback) {
+ var path = '/api/v1/pods/quitfriends'
+
+ // The first pod make friend with the third
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(204)
+ .end(function (err, res) {
+ if (err) throw err
+
+ // Wait for the request between pods
+ setTimeout(callback, 1000)
+ })
+}
+
+function removeVideo (url, id, end) {
+ var path = '/api/v1/videos'
+
+ request(url)
+ .delete(path + '/' + id)
+ .set('Accept', 'application/json')
+ .expect(204)
+ .end(end)
+}
+
+function flushAndRunMultipleServers (total_servers, serversRun) {
+ var apps = []
+ var urls = []
+ var i = 0
+
+ function anotherServerDone (number, app, url) {
+ apps[number - 1] = app
+ urls[number - 1] = url
+ i++
+ if (i === total_servers) {
+ serversRun(apps, urls)
+ }
+ }
+
+ flushTests(function () {
+ for (var j = 1; j <= total_servers; j++) {
+ (function (k) { // TODO: ES6 with let
+ // For the virtual buffer
+ setTimeout(function () {
+ runServer(k, function (app, url) {
+ anotherServerDone(k, app, url)
+ })
+ }, 1000 * k)
+ })(j)
+ }
+ })
+}
+
+function runServer (number, callback) {
+ var port = 9000 + number
+ var server_run_string = {
+ 'Connected to mongodb': false,
+ 'Server listening on port': false
+ }
+
+ // Share the environment
+ var env = Object.create(process.env)
+ env.NODE_ENV = 'test'
+ env.NODE_APP_INSTANCE = number
+ var options = {
+ silent: true,
+ env: env,
+ detached: true
+ }
+
+ var app = fork(pathUtils.join(__dirname, '../../server.js'), [], options)
+ app.stdout.on('data', function onStdout (data) {
+ var dont_continue = false
+ // Check if all required sentences are here
+ for (var key of Object.keys(server_run_string)) {
+ if (data.toString().indexOf(key) !== -1) server_run_string[key] = true
+ if (server_run_string[key] === false) dont_continue = true
+ }
+
+ // If no, there is maybe one thing not already initialized (mongodb...)
+ if (dont_continue === true) return
+
+ app.stdout.removeListener('data', onStdout)
+ callback(app, 'http://localhost:' + port)
+ })
+}
+
+function searchVideo (url, search, end) {
+ var path = '/api/v1/videos'
+
+ request(url)
+ .get(path + '/search/' + search)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function uploadVideo (url, name, description, fixture, end) {
+ var path = '/api/v1/videos'
+
+ request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+ .field('name', name)
+ .field('description', description)
+ .attach('input_video', pathUtils.join(__dirname, 'fixtures', fixture))
+ .expect(201)
+ .end(end)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = testUtils
--- /dev/null
+;(function () {
+ 'use strict'
+
+ // Order of the tests we want to execute
+ require('./api/')
+})()
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var pathUtils = require('path')
-var request = require('supertest')
-
-var utils = require('./utils')
-
-describe('Test parameters validator', function () {
- var app = null
- var url = ''
-
- function makePostRequest (path, fields, attach, done, fail) {
- var status_code = 400
- if (fail !== undefined && fail === false) status_code = 200
-
- var req = request(url)
- .post(path)
- .set('Accept', 'application/json')
-
- Object.keys(fields).forEach(function (field) {
- var value = fields[field]
- req.field(field, value)
- })
-
- req.expect(status_code, done)
- }
-
- function makePostBodyRequest (path, fields, done, fail) {
- var status_code = 400
- if (fail !== undefined && fail === false) status_code = 200
-
- request(url)
- .post(path)
- .set('Accept', 'application/json')
- .send(fields)
- .expect(status_code, done)
- }
-
- // ---------------------------------------------------------------
-
- before(function (done) {
- this.timeout(20000)
-
- async.series([
- function (next) {
- utils.flushTests(next)
- },
- function (next) {
- utils.runServer(1, function (app1, url1) {
- app = app1
- url = url1
- next()
- })
- }
- ], done)
- })
-
- describe('Of the pods API', function () {
- var path = '/api/v1/pods/'
-
- describe('When adding a pod', function () {
- it('Should fail with nothing', function (done) {
- var data = {}
- makePostBodyRequest(path, data, done)
- })
-
- it('Should fail without public key', function (done) {
- var data = {
- data: {
- url: 'http://coucou.com'
- }
- }
- makePostBodyRequest(path, data, done)
- })
-
- it('Should fail without an url', function (done) {
- var data = {
- data: {
- publicKey: 'mysuperpublickey'
- }
- }
- makePostBodyRequest(path, data, done)
- })
-
- it('Should fail with an incorrect url', function (done) {
- var data = {
- data: {
- url: 'coucou.com',
- publicKey: 'mysuperpublickey'
- }
- }
- makePostBodyRequest(path, data, function () {
- data.data.url = 'http://coucou'
- makePostBodyRequest(path, data, function () {
- data.data.url = 'coucou'
- makePostBodyRequest(path, data, done)
- })
- })
- })
-
- it('Should succeed with the correct parameters', function (done) {
- var data = {
- data: {
- url: 'http://coucou.com',
- publicKey: 'mysuperpublickey'
- }
- }
- makePostBodyRequest(path, data, done, false)
- })
- })
- })
-
- describe('Of the videos API', function () {
- var path = '/api/v1/videos/'
-
- describe('When searching a video', function () {
- it('Should fail with nothing', function (done) {
- request(url)
- .get(pathUtils.join(path, 'search'))
- .set('Accept', 'application/json')
- .expect(400, done)
- })
- })
-
- describe('When adding a video', function () {
- it('Should fail with nothing', function (done) {
- var data = {}
- var attach = {}
- makePostRequest(path, data, attach, done)
- })
-
- it('Should fail without name', function (done) {
- var data = {
- description: 'my super description'
- }
- var attach = {
- 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, data, attach, done)
- })
-
- it('Should fail with a long name', function (done) {
- var data = {
- name: 'My very very very very very very very very very very very very very very very very long name',
- description: 'my super description'
- }
- var attach = {
- 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, data, attach, done)
- })
-
- it('Should fail without description', function (done) {
- var data = {
- name: 'my super name'
- }
- var attach = {
- 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, data, attach, done)
- })
-
- it('Should fail with a long description', function (done) {
- var data = {
- name: 'my super name',
- description: 'my super description which is very very very very very very very very very very very very very very' +
- 'very very very very very very very very very very very very very very very very very very very very very' +
- 'very very very very very very very very very very very very very very very long'
- }
- var attach = {
- 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, data, attach, done)
- })
-
- it('Should fail without an input file', function (done) {
- var data = {
- name: 'my super name',
- description: 'my super description'
- }
- var attach = {}
- makePostRequest(path, data, attach, done)
- })
-
- it('Should fail without an incorrect input file', function (done) {
- var data = {
- name: 'my super name',
- description: 'my super description'
- }
- var attach = {
- 'input_video': pathUtils.join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
- }
- makePostRequest(path, data, attach, done)
- })
-
- it('Should succeed with the correct parameters', function (done) {
- var data = {
- name: 'my super name',
- description: 'my super description'
- }
- var attach = {
- 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, data, attach, function () {
- attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
- makePostRequest(path, data, attach, function () {
- attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
- makePostRequest(path, data, attach, done, true)
- }, true)
- }, true)
- })
- })
-
- describe('When getting a video', function () {
- it('Should return the list of the videos with nothing', function (done) {
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(0)
-
- done()
- })
- })
-
- it('Should fail without a mongodb id', function (done) {
- request(url)
- .get(path + 'coucou')
- .set('Accept', 'application/json')
- .expect(400, done)
- })
-
- it('Should return 404 with an incorrect video', function (done) {
- request(url)
- .get(path + '123456789012345678901234')
- .set('Accept', 'application/json')
- .expect(404, done)
- })
-
- it('Should succeed with the correct parameters')
- })
-
- describe('When removing a video', function () {
- it('Should have 404 with nothing', function (done) {
- request(url)
- .delete(path)
- .expect(404, done)
- })
-
- it('Should fail without a mongodb id', function (done) {
- request(url)
- .delete(path + 'hello')
- .expect(400, done)
- })
-
- it('Should fail with a video which does not exist', function (done) {
- request(url)
- .delete(path + '123456789012345678901234')
- .expect(404, done)
- })
-
- it('Should fail with a video of another pod')
-
- it('Should succeed with the correct parameters')
- })
- })
-
- describe('Of the remote videos API', function () {
- describe('When making a secure request', function () {
- it('Should check a secure request')
- })
-
- describe('When adding a video', function () {
- it('Should check when adding a video')
- })
-
- describe('When removing a video', function () {
- it('Should check when removing a video')
- })
- })
-
- after(function (done) {
- process.kill(-app.pid)
-
- // Keep the logs if the test failed
- if (this.ok) {
- utils.flushTests(done)
- } else {
- done()
- }
- })
-})
+++ /dev/null
-this is a fake video mouahahah
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-
-var utils = require('./utils')
-
-describe('Test advanced friends', function () {
- var apps = []
- var urls = []
-
- function makeFriends (pod_number, callback) {
- return utils.makeFriends(urls[pod_number - 1], callback)
- }
-
- function quitFriends (pod_number, callback) {
- return utils.quitFriends(urls[pod_number - 1], callback)
- }
-
- function getFriendsList (pod_number, end) {
- return utils.getFriendsList(urls[pod_number - 1], end)
- }
-
- function uploadVideo (pod_number, callback) {
- var name = 'my super video'
- var description = 'my super description'
- var fixture = 'video_short.webm'
-
- return utils.uploadVideo(urls[pod_number - 1], name, description, fixture, callback)
- }
-
- function getVideos (pod_number, callback) {
- return utils.getVideosList(urls[pod_number - 1], callback)
- }
-
- // ---------------------------------------------------------------
-
- before(function (done) {
- this.timeout(30000)
- utils.flushAndRunMultipleServers(6, function (apps_run, urls_run) {
- apps = apps_run
- urls = urls_run
- done()
- })
- })
-
- it('Should make friends with two pod each in a different group', function (done) {
- this.timeout(20000)
-
- async.series([
- // Pod 3 makes friend with the first one
- function (next) {
- makeFriends(3, next)
- },
- // Pod 4 makes friend with the second one
- function (next) {
- makeFriends(4, next)
- },
- // Now if the fifth wants to make friends with the third et the first
- function (next) {
- makeFriends(5, next)
- },
- function (next) {
- setTimeout(next, 11000)
- }],
- function (err) {
- if (err) throw err
-
- // It should have 0 friends
- getFriendsList(5, function (err, res) {
- if (err) throw err
-
- expect(res.body.length).to.equal(0)
-
- done()
- })
- }
- )
- })
-
- it('Should quit all friends', function (done) {
- this.timeout(10000)
-
- async.series([
- function (next) {
- quitFriends(1, next)
- },
- function (next) {
- quitFriends(2, next)
- }],
- function (err) {
- if (err) throw err
-
- async.each([ 1, 2, 3, 4, 5, 6 ], function (i, callback) {
- getFriendsList(i, function (err, res) {
- if (err) throw err
-
- expect(res.body.length).to.equal(0)
-
- callback()
- })
- }, done)
- }
- )
- })
-
- it('Should make friends with the pods 1, 2, 3', function (done) {
- this.timeout(150000)
-
- async.series([
- // Pods 1, 2, 3 and 4 become friends
- function (next) {
- makeFriends(2, next)
- },
- function (next) {
- makeFriends(1, next)
- },
- function (next) {
- makeFriends(4, next)
- },
- // Kill pod 4
- function (next) {
- apps[3].kill()
- next()
- },
- // Expulse pod 4 from pod 1 and 2
- function (next) {
- uploadVideo(1, next)
- },
- function (next) {
- uploadVideo(2, next)
- },
- function (next) {
- setTimeout(next, 11000)
- },
- function (next) {
- uploadVideo(1, next)
- },
- function (next) {
- uploadVideo(2, next)
- },
- function (next) {
- setTimeout(next, 20000)
- },
- // Rerun server 4
- function (next) {
- utils.runServer(4, function (app, url) {
- apps[3] = app
- next()
- })
- },
- function (next) {
- getFriendsList(4, function (err, res) {
- if (err) throw err
-
- // Pod 4 didn't know pod 1 and 2 removed it
- expect(res.body.length).to.equal(3)
-
- next()
- })
- },
- // Pod 6 ask pod 1, 2 and 3
- function (next) {
- makeFriends(6, next)
- }],
- function (err) {
- if (err) throw err
-
- getFriendsList(6, function (err, res) {
- if (err) throw err
-
- // Pod 4 should not be our friend
- var result = res.body
- expect(result.length).to.equal(3)
- for (var pod of result) {
- expect(pod.url).not.equal(urls[3])
- }
-
- done()
- })
- }
- )
- })
-
- it('Should pod 1 quit friends', function (done) {
- this.timeout(25000)
-
- async.series([
- // Upload a video on server 3 for aditionnal tests
- function (next) {
- uploadVideo(3, next)
- },
- function (next) {
- setTimeout(next, 15000)
- },
- function (next) {
- quitFriends(1, next)
- },
- // Remove pod 1 from pod 2
- function (next) {
- getVideos(1, function (err, res) {
- if (err) throw err
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(2)
-
- next()
- })
- }],
- function (err) {
- if (err) throw err
-
- getVideos(2, function (err, res) {
- if (err) throw err
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(3)
- done()
- })
- }
- )
- })
-
- it('Should make friends between pod 1 and 2 and exchange their videos', function (done) {
- this.timeout(20000)
- makeFriends(1, function () {
- setTimeout(function () {
- getVideos(1, function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(5)
-
- done()
- })
- }, 5000)
- })
- })
-
- after(function (done) {
- apps.forEach(function (app) {
- process.kill(-app.pid)
- })
-
- if (this.ok) {
- utils.flushTests(done)
- } else {
- done()
- }
- })
-})
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var request = require('supertest')
-
-var utils = require('./utils')
-
-describe('Test basic friends', function () {
- var apps = []
- var urls = []
-
- function testMadeFriends (urls, url_to_test, callback) {
- var friends = []
- for (var i = 0; i < urls.length; i++) {
- if (urls[i] === url_to_test) continue
- friends.push(urls[i])
- }
-
- utils.getFriendsList(url_to_test, function (err, res) {
- if (err) throw err
-
- var result = res.body
- var result_urls = [ result[0].url, result[1].url ]
- expect(result).to.be.an('array')
- expect(result.length).to.equal(2)
- expect(result_urls[0]).to.not.equal(result_urls[1])
-
- var error_string = 'Friends url do not correspond for ' + url_to_test
- expect(friends).to.contain(result_urls[0], error_string)
- expect(friends).to.contain(result_urls[1], error_string)
- callback()
- })
- }
-
- // ---------------------------------------------------------------
-
- before(function (done) {
- this.timeout(20000)
- utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
- apps = apps_run
- urls = urls_run
- done()
- })
- })
-
- it('Should not have friends', function (done) {
- async.each(urls, function (url, callback) {
- utils.getFriendsList(url, function (err, res) {
- if (err) throw err
-
- var result = res.body
- expect(result).to.be.an('array')
- expect(result.length).to.equal(0)
- callback()
- })
- }, done)
- })
-
- it('Should make friends', function (done) {
- this.timeout(10000)
-
- var path = '/api/v1/pods/makefriends'
-
- async.series([
- // The second pod make friend with the third
- function (next) {
- request(urls[1])
- .get(path)
- .set('Accept', 'application/json')
- .expect(204)
- .end(next)
- },
- // Wait for the request between pods
- function (next) {
- setTimeout(next, 1000)
- },
- // The second pod should have the third as a friend
- function (next) {
- utils.getFriendsList(urls[1], function (err, res) {
- if (err) throw err
-
- var result = res.body
- expect(result).to.be.an('array')
- expect(result.length).to.equal(1)
- expect(result[0].url).to.be.equal(urls[2])
-
- next()
- })
- },
- // Same here, the third pod should have the second pod as a friend
- function (next) {
- utils.getFriendsList(urls[2], function (err, res) {
- if (err) throw err
-
- var result = res.body
- expect(result).to.be.an('array')
- expect(result.length).to.equal(1)
- expect(result[0].url).to.be.equal(urls[1])
-
- next()
- })
- },
- // Finally the first pod make friend with the second pod
- function (next) {
- request(urls[0])
- .get(path)
- .set('Accept', 'application/json')
- .expect(204)
- .end(next)
- },
- // Wait for the request between pods
- function (next) {
- setTimeout(next, 1000)
- }
- ],
- // Now each pod should be friend with the other ones
- function (err) {
- if (err) throw err
- async.each(urls, function (url, callback) {
- testMadeFriends(urls, url, callback)
- }, done)
- })
- })
-
- it('Should not be allowed to make friend again', function (done) {
- utils.makeFriends(urls[1], 409, done)
- })
-
- it('Should quit friends of pod 2', function (done) {
- async.series([
- // Pod 1 quit friends
- function (next) {
- utils.quitFriends(urls[1], next)
- },
- // Pod 1 should not have friends anymore
- function (next) {
- utils.getFriendsList(urls[1], function (err, res) {
- if (err) throw err
-
- var result = res.body
- expect(result).to.be.an('array')
- expect(result.length).to.equal(0)
-
- next()
- })
- },
- // Other pods shouldn't have pod 1 too
- function (next) {
- async.each([ urls[0], urls[2] ], function (url, callback) {
- utils.getFriendsList(url, function (err, res) {
- if (err) throw err
-
- var result = res.body
- expect(result).to.be.an('array')
- expect(result.length).to.equal(1)
- expect(result[0].url).not.to.be.equal(urls[1])
- callback()
- })
- }, next)
- }
- ], done)
- })
-
- it('Should allow pod 2 to make friend again', function (done) {
- utils.makeFriends(urls[1], function () {
- async.each(urls, function (url, callback) {
- testMadeFriends(urls, url, callback)
- }, done)
- })
- })
-
- after(function (done) {
- apps.forEach(function (app) {
- process.kill(-app.pid)
- })
-
- if (this.ok) {
- utils.flushTests(done)
- } else {
- done()
- }
- })
-})
+++ /dev/null
-'use strict'
-
-// Order of the tests we want to execute
-require('./checkParams')
-require('./friendsBasic')
-require('./singlePod')
-require('./multiplePods')
-require('./friendsAdvanced')
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var pathUtils = require('path')
-
-var utils = require('./utils')
-var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
-webtorrent.silent = true
-
-describe('Test multiple pods', function () {
- var apps = []
- var urls = []
- var to_remove = []
-
- before(function (done) {
- this.timeout(30000)
-
- async.series([
- // Run servers
- function (next) {
- utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
- apps = apps_run
- urls = urls_run
- next()
- })
- },
- // The second pod make friend with the third
- function (next) {
- utils.makeFriends(urls[1], next)
- },
- // Wait for the request between pods
- function (next) {
- setTimeout(next, 10000)
- },
- // Pod 1 make friends too
- function (next) {
- utils.makeFriends(urls[0], next)
- },
- function (next) {
- webtorrent.create({ host: 'client', port: '1' }, next)
- }
- ], done)
- })
-
- it('Should not have videos for all pods', function (done) {
- async.each(urls, function (url, callback) {
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(0)
-
- callback()
- })
- }, done)
- })
-
- describe('Should upload the video and propagate on each pod', function () {
- it('Should upload the video on pod 1 and propagate on each pod', function (done) {
- this.timeout(15000)
-
- async.series([
- function (next) {
- utils.uploadVideo(urls[0], 'my super name for pod 1', 'my super description for pod 1', 'video_short1.webm', next)
- },
- function (next) {
- setTimeout(next, 11000)
- }],
- // All pods should have this video
- function (err) {
- if (err) throw err
-
- async.each(urls, function (url, callback) {
- var base_magnet = null
-
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- var videos = res.body
- expect(videos).to.be.an('array')
- expect(videos.length).to.equal(1)
- var video = videos[0]
- expect(video.name).to.equal('my super name for pod 1')
- expect(video.description).to.equal('my super description for pod 1')
- expect(video.podUrl).to.equal('http://localhost:9001')
- expect(video.magnetUri).to.exist
-
- // All pods should have the same magnet Uri
- if (base_magnet === null) {
- base_magnet = video.magnetUri
- } else {
- expect(video.magnetUri).to.equal.magnetUri
- }
-
- callback()
- })
- }, done)
- }
- )
- })
-
- it('Should upload the video on pod 2 and propagate on each pod', function (done) {
- this.timeout(15000)
-
- async.series([
- function (next) {
- utils.uploadVideo(urls[1], 'my super name for pod 2', 'my super description for pod 2', 'video_short2.webm', next)
- },
- function (next) {
- setTimeout(next, 11000)
- }],
- // All pods should have this video
- function (err) {
- if (err) throw err
-
- async.each(urls, function (url, callback) {
- var base_magnet = null
-
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- var videos = res.body
- expect(videos).to.be.an('array')
- expect(videos.length).to.equal(2)
- var video = videos[1]
- expect(video.name).to.equal('my super name for pod 2')
- expect(video.description).to.equal('my super description for pod 2')
- expect(video.podUrl).to.equal('http://localhost:9002')
- expect(video.magnetUri).to.exist
-
- // All pods should have the same magnet Uri
- if (base_magnet === null) {
- base_magnet = video.magnetUri
- } else {
- expect(video.magnetUri).to.equal.magnetUri
- }
-
- callback()
- })
- }, done)
- }
- )
- })
-
- it('Should upload two videos on pod 3 and propagate on each pod', function (done) {
- this.timeout(30000)
-
- async.series([
- function (next) {
- utils.uploadVideo(urls[2], 'my super name for pod 3', 'my super description for pod 3', 'video_short3.webm', next)
- },
- function (next) {
- utils.uploadVideo(urls[2], 'my super name for pod 3-2', 'my super description for pod 3-2', 'video_short.webm', next)
- },
- function (next) {
- setTimeout(next, 22000)
- }],
- function (err) {
- if (err) throw err
-
- var base_magnet = null
- // All pods should have this video
- async.each(urls, function (url, callback) {
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- var videos = res.body
- expect(videos).to.be.an('array')
- expect(videos.length).to.equal(4)
- var video = videos[2]
- expect(video.name).to.equal('my super name for pod 3')
- expect(video.description).to.equal('my super description for pod 3')
- expect(video.podUrl).to.equal('http://localhost:9003')
- expect(video.magnetUri).to.exist
-
- video = videos[3]
- expect(video.name).to.equal('my super name for pod 3-2')
- expect(video.description).to.equal('my super description for pod 3-2')
- expect(video.podUrl).to.equal('http://localhost:9003')
- expect(video.magnetUri).to.exist
-
- // All pods should have the same magnet Uri
- if (base_magnet === null) {
- base_magnet = video.magnetUri
- } else {
- expect(video.magnetUri).to.equal.magnetUri
- }
-
- callback()
- })
- }, done)
- }
- )
- })
- })
-
- describe('Should seed the uploaded video', function () {
- it('Should add the file 1 by asking pod 3', function (done) {
- // Yes, this could be long
- this.timeout(200000)
-
- utils.getVideosList(urls[2], function (err, res) {
- if (err) throw err
-
- var video = res.body[0]
- to_remove.push(res.body[2]._id)
- to_remove.push(res.body[3]._id)
-
- webtorrent.add(video.magnetUri, function (torrent) {
- expect(torrent.files).to.exist
- expect(torrent.files.length).to.equal(1)
- expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
- done()
- })
- })
- })
-
- it('Should add the file 2 by asking pod 1', function (done) {
- // Yes, this could be long
- this.timeout(200000)
-
- utils.getVideosList(urls[0], function (err, res) {
- if (err) throw err
-
- var video = res.body[1]
-
- webtorrent.add(video.magnetUri, function (torrent) {
- expect(torrent.files).to.exist
- expect(torrent.files.length).to.equal(1)
- expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
- done()
- })
- })
- })
-
- it('Should add the file 3 by asking pod 2', function (done) {
- // Yes, this could be long
- this.timeout(200000)
-
- utils.getVideosList(urls[1], function (err, res) {
- if (err) throw err
-
- var video = res.body[2]
-
- webtorrent.add(video.magnetUri, function (torrent) {
- expect(torrent.files).to.exist
- expect(torrent.files.length).to.equal(1)
- expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
- done()
- })
- })
- })
-
- it('Should add the file 3-2 by asking pod 1', function (done) {
- // Yes, this could be long
- this.timeout(200000)
-
- utils.getVideosList(urls[0], function (err, res) {
- if (err) throw err
-
- var video = res.body[3]
-
- webtorrent.add(video.magnetUri, function (torrent) {
- expect(torrent.files).to.exist
- expect(torrent.files.length).to.equal(1)
- expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
- done()
- })
- })
- })
-
- it('Should remove the file 3 and 3-2 by asking pod 3', function (done) {
- this.timeout(15000)
-
- async.series([
- function (next) {
- utils.removeVideo(urls[2], to_remove[0], next)
- },
- function (next) {
- utils.removeVideo(urls[2], to_remove[1], next)
- }],
- function (err) {
- if (err) throw err
- setTimeout(done, 11000)
- }
- )
- })
-
- it('Should have videos 1 and 3 on each pod', function (done) {
- async.each(urls, function (url, callback) {
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- var videos = res.body
- expect(videos).to.be.an('array')
- expect(videos.length).to.equal(2)
- expect(videos[0]._id).not.to.equal(videos[1]._id)
- expect(videos[0]._id).not.to.equal(to_remove[0])
- expect(videos[1]._id).not.to.equal(to_remove[0])
- expect(videos[0]._id).not.to.equal(to_remove[1])
- expect(videos[1]._id).not.to.equal(to_remove[1])
-
- callback()
- })
- }, done)
- })
- })
-
- after(function (done) {
- apps.forEach(function (app) {
- process.kill(-app.pid)
- })
- process.kill(-webtorrent.app.pid)
-
- // Keep the logs if the test failed
- if (this.ok) {
- utils.flushTests(done)
- } else {
- done()
- }
- })
-})
+++ /dev/null
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var fs = require('fs')
-var pathUtils = require('path')
-
-var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
-webtorrent.silent = true
-
-var utils = require('./utils')
-
-describe('Test a single pod', function () {
- var app = null
- var url = ''
- var video_id = -1
-
- before(function (done) {
- this.timeout(20000)
-
- async.series([
- function (next) {
- utils.flushTests(next)
- },
- function (next) {
- utils.runServer(1, function (app1, url1) {
- app = app1
- url = url1
- next()
- })
- },
- function (next) {
- webtorrent.create({ host: 'client', port: '1' }, next)
- }
- ], done)
- })
-
- it('Should not have videos', function (done) {
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(0)
-
- done()
- })
- })
-
- it('Should upload the video', function (done) {
- this.timeout(5000)
- utils.uploadVideo(url, 'my super name', 'my super description', 'video_short.webm', done)
- })
-
- it('Should seed the uploaded video', function (done) {
- // Yes, this could be long
- this.timeout(60000)
-
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(1)
-
- var video = res.body[0]
- expect(video.name).to.equal('my super name')
- expect(video.description).to.equal('my super description')
- expect(video.podUrl).to.equal('http://localhost:9001')
- expect(video.magnetUri).to.exist
-
- video_id = video._id
-
- webtorrent.add(video.magnetUri, function (torrent) {
- expect(torrent.files).to.exist
- expect(torrent.files.length).to.equal(1)
- expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
- done()
- })
- })
- })
-
- it('Should search the video', function (done) {
- utils.searchVideo(url, 'my', function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(1)
-
- var video = res.body[0]
- expect(video.name).to.equal('my super name')
- expect(video.description).to.equal('my super description')
- expect(video.podUrl).to.equal('http://localhost:9001')
- expect(video.magnetUri).to.exist
-
- done()
- })
- })
-
- it('Should not find a search', function (done) {
- utils.searchVideo(url, 'hello', function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(0)
-
- done()
- })
- })
-
- it('Should remove the video', function (done) {
- utils.removeVideo(url, video_id, function (err) {
- if (err) throw err
-
- fs.readdir(pathUtils.join(__dirname, '../../test1/uploads/'), function (err, files) {
- if (err) throw err
-
- expect(files.length).to.equal(0)
- done()
- })
- })
- })
-
- it('Should not have videos', function (done) {
- utils.getVideosList(url, function (err, res) {
- if (err) throw err
-
- expect(res.body).to.be.an('array')
- expect(res.body.length).to.equal(0)
-
- done()
- })
- })
-
- after(function (done) {
- process.kill(-app.pid)
- process.kill(-webtorrent.app.pid)
-
- // Keep the logs if the test failed
- if (this.ok) {
- utils.flushTests(done)
- } else {
- done()
- }
- })
-})
+++ /dev/null
-'use strict'
-
-var child_process = require('child_process')
-var exec = child_process.exec
-var fork = child_process.fork
-var pathUtils = require('path')
-var request = require('supertest')
-
-var testUtils = {
- flushTests: flushTests,
- getFriendsList: getFriendsList,
- getVideosList: getVideosList,
- makeFriends: makeFriends,
- quitFriends: quitFriends,
- removeVideo: removeVideo,
- flushAndRunMultipleServers: flushAndRunMultipleServers,
- runServer: runServer,
- searchVideo: searchVideo,
- uploadVideo: uploadVideo
-}
-
-// ---------------------- Export functions --------------------
-
-function flushTests (callback) {
- exec(pathUtils.join(__dirname, '../../scripts/clean_test.sh'), callback)
-}
-
-function getFriendsList (url, end) {
- var path = '/api/v1/pods/'
-
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function getVideosList (url, end) {
- var path = '/api/v1/videos'
-
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function makeFriends (url, expected_status, callback) {
- if (!callback) {
- callback = expected_status
- expected_status = 204
- }
-
- var path = '/api/v1/pods/makefriends'
-
- // The first pod make friend with the third
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(expected_status)
- .end(function (err, res) {
- if (err) throw err
-
- // Wait for the request between pods
- setTimeout(callback, 1000)
- })
-}
-
-function quitFriends (url, callback) {
- var path = '/api/v1/pods/quitfriends'
-
- // The first pod make friend with the third
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(204)
- .end(function (err, res) {
- if (err) throw err
-
- // Wait for the request between pods
- setTimeout(callback, 1000)
- })
-}
-
-function removeVideo (url, id, end) {
- var path = '/api/v1/videos'
-
- request(url)
- .delete(path + '/' + id)
- .set('Accept', 'application/json')
- .expect(204)
- .end(end)
-}
-
-function flushAndRunMultipleServers (total_servers, serversRun) {
- var apps = []
- var urls = []
- var i = 0
-
- function anotherServerDone (number, app, url) {
- apps[number - 1] = app
- urls[number - 1] = url
- i++
- if (i === total_servers) {
- serversRun(apps, urls)
- }
- }
-
- flushTests(function () {
- for (var j = 1; j <= total_servers; j++) {
- (function (k) { // TODO: ES6 with let
- // For the virtual buffer
- setTimeout(function () {
- runServer(k, function (app, url) {
- anotherServerDone(k, app, url)
- })
- }, 1000 * k)
- })(j)
- }
- })
-}
-
-function runServer (number, callback) {
- var port = 9000 + number
- var server_run_string = {
- 'Connected to mongodb': false,
- 'Server listening on port': false
- }
-
- // Share the environment
- var env = Object.create(process.env)
- env.NODE_ENV = 'test'
- env.NODE_APP_INSTANCE = number
- var options = {
- silent: true,
- env: env,
- detached: true
- }
-
- var app = fork(pathUtils.join(__dirname, '../../server.js'), [], options)
- app.stdout.on('data', function onStdout (data) {
- var dont_continue = false
- // Check if all required sentences are here
- for (var key of Object.keys(server_run_string)) {
- if (data.toString().indexOf(key) !== -1) server_run_string[key] = true
- if (server_run_string[key] === false) dont_continue = true
- }
-
- // If no, there is maybe one thing not already initialized (mongodb...)
- if (dont_continue === true) return
-
- app.stdout.removeListener('data', onStdout)
- callback(app, 'http://localhost:' + port)
- })
-}
-
-function searchVideo (url, search, end) {
- var path = '/api/v1/videos'
-
- request(url)
- .get(path + '/search/' + search)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function uploadVideo (url, name, description, fixture, end) {
- var path = '/api/v1/videos'
-
- request(url)
- .post(path)
- .set('Accept', 'application/json')
- .field('name', name)
- .field('description', description)
- .attach('input_video', pathUtils.join(__dirname, 'fixtures', fixture))
- .expect(201)
- .end(end)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = testUtils
+++ /dev/null
-;(function () {
- 'use strict'
-
- // Order of the tests we want to execute
- require('./api/')
-})()
+++ /dev/null
-extends layout
-
-block content
- h1= message
- if error
- h2= error.status
- pre #{error.stack}
-
+++ /dev/null
-footer
-
- | PeerTube, CopyLeft 2015
+++ /dev/null
-.navbar.navbar-default
- .container-fluid
- .navbar-header
- a.navbar-brand(href='/') PeerTube
- .navbar-header
- form.navbar-form(role='search')
- .form-group.search-group
- input.form-control#search-video(type='text' name='search_video' placeholder='Search a video...')
- a.search-btn.btn.btn-link.glyphicon.glyphicon-search(type='submit')
\ No newline at end of file
+++ /dev/null
-extends layout
-
-block content
- div(class='container')
- div(class='row')
- include panel
-
- div(id='ajax_load' class='col-md-9')
-
-
- include footer
-
- // build:js /javascripts/global.min.js
- script(src='/javascripts/bundle.js')
- // endbuild
+++ /dev/null
-doctype html
-html
- head
- link(rel='icon' type='image/png' href='/images/favicon.png')
- meta(name='viewport' content='width=device-width, initial-scale=1')
- title= title
-
- // build:css /stylesheets/global.min.css
- each file in [ 'global' ]
- link(rel='stylesheet', href='/stylesheets/' + file + '.css')
- // endbuild
-
- body
- include header
- block content
+++ /dev/null
-menu(class='col-md-2')
-
- div(id='panel_get_videos' class='panel_button')
- span(class='glyphicon glyphicon-list')
- | Get videos
-
- div(id='panel_upload_video' class='panel_button')
- span(class='glyphicon glyphicon-cloud-upload')
- | Upload a video
-
- div(id='panel_make_friends' class='panel_button')
- span(class='glyphicon glyphicon-user')
- | Make friends
-
- div(id='panel_quit_friends' class='panel_button')
- span(class='glyphicon glyphicon-plane')
- | Quit friends