├── .gitignore ├── app.py ├── db_create.py ├── forms.py ├── models.py ├── readme.md ├── requirements.txt ├── static ├── add.js ├── display.js ├── filetransfer.js └── search.js ├── templates ├── add.html ├── base.html ├── filetransfer.html ├── index.html ├── login.html ├── newuser.html ├── search.html └── sse.html └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | *.pyc 3 | config.py 4 | /static/lib/adapter.js 5 | /static/favicon.ico 6 | /static/jquery* -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import json 4 | 5 | from flask import Flask, Response, render_template, url_for, jsonify, request, session, redirect, flash 6 | from werkzeug import secure_filename 7 | from flask.ext.sqlalchemy import SQLAlchemy, BaseQuery 8 | from flask.ext.bcrypt import Bcrypt 9 | from flask.ext.socketio import SocketIO, emit, send, join_room, leave_room 10 | from flask.ext.login import LoginManager, login_user, login_required, logout_user, current_user 11 | from sqlalchemy_searchable import search, make_searchable 12 | import gevent 13 | import gevent.monkey 14 | from gevent.pywsgi import WSGIServer 15 | gevent.monkey.patch_all() 16 | 17 | from fuzzywuzzy import process 18 | from forms import * 19 | 20 | 21 | #Creating the flask app and pointing to the config 22 | app = Flask(__name__) 23 | app.config.from_object('config.DevelopmentConfig') 24 | 25 | #create database object 26 | db = SQLAlchemy(app) 27 | make_searchable() 28 | 29 | #create brypt object 30 | bcrypt = Bcrypt(app) 31 | 32 | #Create socketio server instance 33 | socketio = SocketIO(app) 34 | 35 | #create instance of Login Manager 36 | login_manager = LoginManager() 37 | login_manager.init_app(app) 38 | 39 | from models import * 40 | 41 | onlineUsers = []; 42 | 43 | 44 | @app.route('/', methods=['GET', 'POST']) 45 | @app.route('/index', methods=['GET', 'POST']) 46 | @app.route('/index/', methods=['GET', 'POST']) 47 | def home(page=1): 48 | if current_user.is_authenticated(): 49 | files = filestable.query.filter_by(ownerhostel = current_user.hostel).paginate(page,app.config["FILES_PER_PAGE"], False) 50 | for i in xrange(0, len(files.items)): 51 | files.items[i].name = files.items[i].name.replace("_"," ") 52 | return render_template('index.html', files=files) 53 | else: 54 | return redirect(url_for('add')) 55 | 56 | 57 | def event_stream(): 58 | count = 0 59 | while True: 60 | gevent.sleep(2) 61 | yield 'data: %s\n\n' % count 62 | count += 1 63 | 64 | 65 | @app.route('/sse_event_source') 66 | def sse_request(): 67 | return Response( 68 | event_stream(), 69 | mimetype='text/event-stream') 70 | 71 | 72 | @app.route('/sse') 73 | def sse_page(): 74 | return render_template('sse.html') 75 | 76 | 77 | @app.route('/add', methods=['GET', 'POST']) 78 | @login_required 79 | def add(): 80 | if request.method == 'POST': 81 | receivedNames = request.json["names"] 82 | receivedSizes = request.json["sizes"] 83 | receivedExtns = request.json["extensions"] 84 | 85 | for i in xrange(0, len(receivedNames)): 86 | securename = secure_filename(receivedNames[i]) 87 | qry = filestable(unicode(securename), receivedExtns[i], receivedSizes[i], 'misc', current_user.id, current_user.hostel, 0) 88 | db.session.add(qry) 89 | db.session.commit() 90 | 91 | result = filestable.query.all() 92 | print result 93 | return render_template('add.html') 94 | return render_template('add.html') 95 | 96 | 97 | login_manager.login_view = "login" 98 | login_manager.login_message = "Please login to view this page" 99 | 100 | 101 | @login_manager.user_loader 102 | def load_user(userid): 103 | return User.query.filter(User.id == int(userid)).first() 104 | 105 | 106 | @app.route('/login', methods=['GET', 'POST']) 107 | def login(): 108 | 109 | error = '' 110 | form = LoginForm(request.form) 111 | if request.method == 'POST': 112 | if form.validate_on_submit(): 113 | user = User.query.filter_by(username=request.form['username']).first() 114 | if user is not None and bcrypt.check_password_hash(user.password, request.form['password']): 115 | #session['logged_in'] = True 116 | result = login_user(user) 117 | print result 118 | flash('You are now logged in.') 119 | return redirect(url_for('home')) 120 | else: 121 | error = "Invalid Credentials, try again" 122 | else: 123 | render_template('login.html', form=form, error=error) 124 | return render_template('login.html', form=form) 125 | 126 | 127 | @app.route('/newuser', methods=['GET', 'POST']) 128 | def newuser(): 129 | form = RegisterForm() 130 | if form.validate_on_submit(): 131 | user = User( 132 | username=form.username.data, 133 | password=form.password.data, 134 | firstname=form.firstname.data, 135 | lastname=form.lastname.data, 136 | hostel=form.hostel.data, 137 | year=form.year.data, 138 | room=form.room.data 139 | ) 140 | db.session.add(user) 141 | db.session.commit() 142 | login_user(user) 143 | return redirect(url_for('home')) 144 | return render_template('newuser.html', form=form) 145 | 146 | 147 | @app.route('/logout') 148 | @login_required 149 | def logout(): 150 | logout_user() 151 | flash('You have been logged out') 152 | return redirect(url_for('login')) 153 | 154 | 155 | @app.route('/search', methods=['GET', 'POST']) 156 | @login_required 157 | def search(): 158 | form = SearchForm() 159 | return render_template('search.html', form=form) 160 | 161 | 162 | @app.route('/results', methods=['GET', 'POST']) 163 | def results(): 164 | if request.method == 'POST': 165 | qry = request.json["query"] 166 | print qry 167 | dbresults = [] 168 | dbresults = filestable.query.filter_by(ownerhostel = current_user.hostel).search(unicode(qry)).all() 169 | i = 0 170 | print 'HELLO ' + str(dbresults) 171 | 172 | if len(dbresults) < 15: 173 | print "Inside" 174 | much = 15 - len(dbresults) 175 | dbresults += filestable.query.search(unicode(qry)).limit(much).all() 176 | 177 | print len(dbresults) 178 | if len(dbresults) > 0: 179 | print 'dbresults is : ' + str(type(dbresults)) 180 | print 'Type of dbresults[0] is : ' + str(type(dbresults[0])) 181 | dbresultsname = [] 182 | print 'AND NOW ' + str(dbresults[0].ownerhostel) 183 | for i in xrange(0, len(dbresults)): 184 | print str(type(dbresults[i])) 185 | dbresultsname.append(str(dbresults[i].name.replace("_"," "))) 186 | i = i + 1 187 | 188 | fuzzyResults = process.extract(unicode(qry),dbresultsname,limit=5) 189 | print 'AND HI ' + str(fuzzyResults) 190 | else: 191 | fuzzyResults = "" 192 | print "Sorry No results" 193 | return jsonify(result = fuzzyResults) 194 | else: 195 | return redirect(url_for('search')) 196 | 197 | 198 | @app.route('/filetransfer/', defaults={'room_name': 'default'}) 199 | @app.route('/filetransfer/') 200 | @login_required 201 | def filetransfer(room_name): 202 | return render_template('filetransfer.html', room_name=room_name) 203 | 204 | 205 | @app.route('/sse') 206 | def sse(): 207 | return redirect(url_for('home')) 208 | 209 | clients = {} 210 | 211 | 212 | def logger(text): 213 | array = ['Message from server: '] 214 | array.append(text) 215 | emit('logger', array) 216 | 217 | 218 | @socketio.on('got connected') 219 | def handle_got_connected(): 220 | print('Received id: ' + str(request.namespace.socket.sessid)) 221 | 222 | 223 | @socketio.on('create or join') 224 | def create_or_join(room): 225 | print 'Received request from clientid' + request.namespace.socket.sessid + ' to create or join room ' + str(room) 226 | if str(room) in clients: 227 | clients[str(room)].append(request.namespace.socket.sessid) 228 | print 'Dictionary being updated' 229 | else: 230 | clients.update({ str(room): [request.namespace.socket.sessid]}) 231 | print 'Dictionary entry being created' 232 | 233 | numClients = len(clients[str(room)]) 234 | print numClients 235 | if numClients <= 1: 236 | join_room(str(room)) 237 | logger('Client ID ' + request.namespace.socket.sessid + ' created room ' + str(room)) 238 | print 'Client ID ' + request.namespace.socket.sessid + ' created room ' + str(room) 239 | emit('created', room, request.namespace.socket.sessid) 240 | elif numClients <= 2: 241 | logger('Client ID ' + request.namespace.socket.sessid + ' joined room ' + str(room)) 242 | print 'Client ID ' + request.namespace.socket.sessid + ' joined room ' + str(room) 243 | join_room(str(room)) 244 | emit('joined', room, request.namespace.socket.sessid) 245 | emit('nowready', room=room) #This sends it to all the clients FROM the server since the socketio 246 | else:#Max 2 clients 247 | print "Room is full" 248 | emit('full', room) 249 | 250 | 251 | 252 | @socketio.on('message') 253 | def message(message): 254 | logger('Client said: ' + str(message)) 255 | emit('message', message, broadcast=True) 256 | 257 | 258 | 259 | @socketio.on('on_disconnect') 260 | def on_disconnect(room): 261 | print 'Client id '+ request.namespace.socket.sessid +' disconnected' 262 | if str(room) in clients: 263 | if request.namespace.socket.sessid in clients[str(room)] is not None: 264 | print 'Removed' 265 | clients[room].remove(request.namespace.socket.sessid) 266 | 267 | 268 | @socketio.on('leave') 269 | def on_leave(room): 270 | if str(room) in clients: 271 | if request.namespace.socket.sessid in clients[str(room)]: 272 | logger('Client id ' + request.namespace.socket.sessid + ' has left the room ' + str(room)) 273 | print 'Client id ' + request.namespace.socket.sessid + ' has left the room ' + str(room) 274 | leave_room(str(room)) 275 | clients[str(room)].remove(request.namespace.socket.sessid) 276 | 277 | 278 | #start the server with the run method 279 | if __name__ == '__main__': 280 | socketio.run(app, host='127.0.0.1', port=4000,policy_server=False) 281 | -------------------------------------------------------------------------------- /db_create.py: -------------------------------------------------------------------------------- 1 | from app import db 2 | from models import * 3 | 4 | #Configure the mappers BEFORE create_all() 5 | db.configure_mappers() 6 | db.create_all() 7 | 8 | 9 | db.session.add(User('admin', 'admin', 'admin', 'admin', 'admin', 'admin', 'admin')) 10 | db.session.add(User('aaa', 'aaa', 'a', 'a', 'agate', '1', 'first')) 11 | db.session.add(User('bbb', 'bbb', 'b', 'b', 'bbb', '2', 'first')) 12 | db.session.add(User('ccc', 'ccc', 'c', 'c', 'agate', '2', 'first')) 13 | db.session.add(filestable(name=u'gravity', filetype='mp4', size='14444', mediatype='movie', ownerid='1', ownerhostel='agate', views=0)) 14 | db.session.commit() -------------------------------------------------------------------------------- /forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import Form 2 | from wtforms import TextField, PasswordField, SelectField 3 | from wtforms.validators import DataRequired, Length, EqualTo 4 | 5 | class LoginForm(Form): 6 | username = TextField('Username', validators=[DataRequired()]) 7 | password = PasswordField('Password', validators=[DataRequired()]) 8 | 9 | 10 | class RegisterForm(Form): 11 | firstname = TextField( 12 | 'firstname', 13 | validators=[DataRequired()] 14 | ) 15 | lastname = TextField( 16 | 'lastname', 17 | validators=[DataRequired()] 18 | ) 19 | hostel = SelectField( 20 | 'hostel', 21 | choices=[('ambera', 'Amber A'), ('amberb', 'Amber B'), ('garneta', 'Garnet A'), ('garnetb', 'Garnet B'), ('garnetc', 'Garnet C'), ('zircona', 'Zircon A'), ('zirconb', 'Zircon B'), ('zirconc', 'Zircon C'), ('agate', 'Agate'), ('diamond', 'Diamond'), ('coral', 'Coral'), ('jade', 'Jade')], 22 | validators=[DataRequired()] 23 | ) 24 | room = TextField( 25 | 'room', 26 | validators=[DataRequired()] 27 | ) 28 | year = SelectField( 29 | 'year', 30 | choices=[('first', 'First'), ('second', 'Second'), ('third', 'Third'), ('fourth', 'Fourth')], 31 | validators=[DataRequired()] 32 | ) 33 | username = TextField( 34 | 'username', 35 | validators=[DataRequired(), Length(min=3, max=25)] 36 | ) 37 | password = PasswordField( 38 | 'password', 39 | validators=[DataRequired(), Length(min=3, max=25)] 40 | ) 41 | confirm = PasswordField( 42 | 'Repeat Password', 43 | validators=[DataRequired(), EqualTo('password', message='Passwords must match.')] 44 | ) 45 | 46 | 47 | class SearchForm(Form): 48 | search = TextField('search', validators=[DataRequired()]) -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from app import db, bcrypt 2 | 3 | from flask.ext.sqlalchemy import SQLAlchemy, BaseQuery 4 | from sqlalchemy_searchable import SearchQueryMixin, make_searchable 5 | from sqlalchemy_utils.types import TSVectorType 6 | 7 | 8 | #For using the searchquery mixin so that the specified fields become searchable 9 | class NameQuery(BaseQuery, SearchQueryMixin): 10 | pass 11 | 12 | 13 | #For holding the information about each file 14 | class filestable(db.Model): 15 | query_class = NameQuery 16 | __tablename__ = 'filestable' 17 | 18 | id = db.Column(db.Integer, primary_key=True) 19 | name = db.Column(db.UnicodeText, nullable=False) 20 | filetype = db.Column(db.String, nullable=False) 21 | size = db.Column(db.String, nullable=False) 22 | #Movie, music, picture,etc 23 | mediatype = db.Column(db.String, nullable=False) 24 | ownerid = db.Column(db.String, nullable=False) 25 | ownerhostel = db.Column(db.String, nullable=False) 26 | views = db.Column(db.Integer, nullable=False) 27 | search_vector = db.Column(TSVectorType('name', 'ownerhostel')) 28 | 29 | def __init__(self, name, filetype, size, mediatype, ownerid, ownerhostel, views): 30 | self.name = name 31 | self.filetype = filetype 32 | self.size = size 33 | self.mediatype = mediatype 34 | self.ownerid = ownerid 35 | self.ownerhostel = ownerhostel 36 | self.views = views 37 | 38 | def __repr__(self): 39 | return " -1; 20 | if (isChrome) { 21 | $("div.instructions").text("Select the folder you wish to scan."); 22 | $("input#files").text('Choose Folder'); 23 | } 24 | else { 25 | $("div.instructions").text("Select the files you wish to scan."); 26 | } 27 | 28 | input.onchange = function(e) { 29 | var movieButton = document.getElementById('movie'); 30 | var musicButton = document.getElementById('music'); 31 | var tvButton = document.getElementById('tv'); 32 | var otherButton = document.getElementById('other'); 33 | var allowedExtns = []; 34 | if(movieButton.checked || tvButton.checked) { 35 | allowedExtns.push('mp4','3gp','avi','flv','m4v','mov','mkv'); 36 | if(movieButton.checked) { 37 | specType = 'movie'; 38 | specNo = 0; 39 | } 40 | if(tvButton.checked) { 41 | specType = 'tv'; 42 | specNo = 1; 43 | } 44 | } 45 | else if(musicButton.checked) { 46 | allowedExtns.push('mp3'); 47 | allowedExtns.push('wav'); 48 | allowedExtns.push('flac'); 49 | allowedExtns.push('la'); 50 | allowedExtns.push('aiff'); 51 | allowedExtns.push('m4a'); 52 | allowedExtns.push('wma'); 53 | allowedExtns.push('aac'); 54 | specType = 'music'; 55 | specNo = 2; 56 | } 57 | else { 58 | specType = 'other'; 59 | specNo = 3; 60 | } 61 | 62 | //Show the Add Files Button 63 | $('#addFiles').show(); 64 | 65 | console.log('Allowed extns : ' + allowedExtns); 66 | 67 | var files = e.target.files; //FileList 68 | $("div#input").append(""); 69 | for (var i = 0, f; f = files[i]; ++i) { 70 | 71 | 72 | console.log("name " + f.name); 73 | console.log("size " + f.size); 74 | console.log("extn " + String(f.name.substr((~-f.name.lastIndexOf(".") >>> 0) + 2 ) )); 75 | 76 | var calextn = f.name.substr((~-f.name.lastIndexOf(".") >>> 0) + 2); 77 | calextn = String(calextn).toLowerCase(); 78 | 79 | if(musicButton.checked || tvButton.checked || movieButton.checked) { 80 | if(allowedExtns.indexOf(calextn) > -1 ) { 81 | console.log('Allowed'); 82 | 83 | //Replac ing spaces with hyphens 84 | var fId = f.name.replace(/\s+/g, "-"); 85 | 86 | $("div#output").append("
"); 87 | $("div." + String(i)).append("\ 88 | \ 89 | "); 91 | document.getElementById(fId).defaultValue = String(f.name); 92 | document.getElementById('mediatype' + String(i)).options[specNo].selected = true; 93 | } 94 | } 95 | else { 96 | //Replacing spaces with hyphens 97 | var fId = f.name.replace(/\s+/g, "-"); 98 | 99 | $("div#output").append("
"); 100 | $("div." + String(i)).append("\ 101 | \ 102 | "); 104 | document.getElementById(fId).defaultValue = String(f.name); 105 | document.getElementById('mediatype' + String(i)).options[specNo].selected = true; 106 | 107 | } 108 | } 109 | $("input[type='text']").attr('size', 70); 110 | 111 | //Sending the Checked Files 112 | $('#addFiles').click( function() { 113 | $('input[type=checkbox]').each(function () { 114 | if(this.checked) { 115 | 116 | var cbId = parseInt(this.id); 117 | 118 | var calextn = files[cbId].name.substr((~-files[cbId].name.lastIndexOf(".") >>> 0) + 2); 119 | calextn = String(calextn).toLowerCase(); 120 | if( calextn === "") { 121 | extns.push('""'); 122 | } 123 | else { 124 | extns.push('"' + calextn + '"'); 125 | } 126 | 127 | sizes.push('"' + String(files[cbId].size) + '"'); 128 | //Pushing the Name 129 | var newName = document.getElementById(this.className).value; 130 | names.push('"' + String(newName) + '"'); 131 | 132 | var mtypeElem = $("select#" + fId); 133 | 134 | mtype.push('"' + + '"'); 135 | 136 | //Hiding the sent textbox, then the checkbox 137 | $('div.' + this.id).hide(800); 138 | $('#' + this.id).hide(800); 139 | } 140 | }); 141 | 142 | var form_data = '{ "names" : [' + names + '], "sizes" : [' + sizes + '], "extensions" : [' + extns + ']}'; 143 | console.log(form_data); 144 | //Converts JSON String to an object, also check JSON Validity of string 145 | var JsonFormData = JSON.parse(form_data); 146 | console.log(JsonFormData); 147 | $.ajax({ 148 | type: "POST", //Since the default is GET 149 | url: url, 150 | async: true, //so that it's asynchronous 151 | processData: false, //So it doesn't automatically get converted to strings 152 | contentType: 'application/json;charset=UTF-8', //So it doesn't set any header 153 | dataType: 'json', 154 | data: JSON.stringify(JsonFormData, null, '\t'), 155 | success: function(data) { 156 | console.log("Data Received!"); 157 | }, 158 | error: function(jqxhr, status, message) { 159 | console.log("Sorry, there seems to have been an erro. Don't worry it's probably our fault :)"); 160 | console.log("Error : " + message); 161 | } 162 | }); 163 | }); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /static/display.js: -------------------------------------------------------------------------------- 1 | function readyFunction(listFromDb) { 2 | for( var i=0; i < listFromDb.length; i++) { 3 | listFromDb[i].name = listFromDb.name.replace(/_/g," "); 4 | console.log(listFromDb[i].name); 5 | } 6 | button.addEventListener('click', requestSend); 7 | function requestSend() { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /static/filetransfer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * Make text sharing work - Done 5 | * Make it close the connection properly - Done 6 | * Sort out room assignment, when the rooms are full 7 | * Make it share files - Done 8 | * Make DataChannels much faster 9 | * Make filesharing work in Chrome - Done 10 | * Fix Browser Interoperability Issues - Done Note: Large files >50MB don't work from CHROME -> Firefox 11 | * Fix issue with restarting -Done 12 | * Make the connectione always work, or add something so user is prompted to reload if it fails 13 | * Integrate with the rest of CampFile 14 | */ 15 | function readyFunction(room_name) { 16 | /*document.querySelector('input[type=file]').onchange = function() { 17 | var file = this.files[0]; 18 | };*/ 19 | 20 | /* Initial Setup */ 21 | var iceServers = { 22 | 'iceServers': [ 23 | {url: 'stun:23.21.150.121'}, 24 | {url:'stun:stun.l.google.com:19302'}, 25 | {url:'stun:stun1.l.google.com:19302'}, 26 | {url:'stun:stun2.l.google.com:19302'}, 27 | {url:'stun:stun3.l.google.com:19302'}, 28 | {url:'stun:stun4.l.google.com:19302'}, 29 | {url:'stun:stun.ekiga.net'}, 30 | {url:'stun:stun.ideasip.com'}, 31 | {url:'stun:stun.iptel.org'}, 32 | {url:'stun:stun.rixtelecom.se'}, 33 | {url:'stun:stun.schlund.de'}, 34 | {url:'stun:stunserver.org'}, 35 | ] 36 | 37 | }; 38 | 39 | var options = { 40 | optional: [ 41 | {DtlsSrtpKeyAgreement: true}//, //For Chrome to work with Firefox 42 | //{RtpDataChannels: true} //For DataChannels to work on Firefox, RTP is now outdated 43 | ] 44 | }; 45 | 46 | var sdpOptions = { 47 | offerToReceiveAudio: false, 48 | offerToReceiveVideo: false 49 | }; 50 | var theFile = null; 51 | var roomURL = document.getElementById('url'); 52 | updateRoomURL(); 53 | var fileInput = document.getElementById('file'); 54 | var downloadLink = document.getElementById('dllink'); 55 | 56 | var restartButton = document.getElementById('restartButton'); 57 | var sendButton = document.getElementById('sendButton'); 58 | var closeButton = document.getElementById('closeButton'); 59 | 60 | restartButton.disabled = true; 61 | sendButton.disabled = true; 62 | closeButton.disabled = true; 63 | restartButton.addEventListener('click', restartConnection); 64 | sendButton.addEventListener('click', sendData); 65 | closeButton.addEventListener('click', closeChannels); 66 | fileInput.addEventListener('change', getFile); 67 | window.addEventListener("unload", handleUnload); 68 | 69 | var restart = false; 70 | var pC = null; 71 | var dataChannel = null; 72 | var isInitiator; 73 | var receiveBuffer = [], receivedSize = 0, size; 74 | 75 | /* Signalling Server */ 76 | var namespace = ''; 77 | var id; 78 | var socket = io.connect($SCRIPT_ROOT + namespace); 79 | 80 | //Only create a room if it's not already there in the URL 81 | var room = room_name; 82 | 83 | 84 | console.log('Room ought to be: ' + room); 85 | 86 | //This seems to be called only after the 'create and join' emit is called rendering it useless 87 | /*socket.on('connect', function() { 88 | console.log(this.socket.sessionid) 89 | socket.emit('got connected'); 90 | }); */ 91 | 92 | socket.on('created', function(room, clientId) { 93 | console.log('Created a room: ' + room + ' - my client id is: ' + clientId); 94 | isInitiator = true; 95 | }); 96 | 97 | socket.on('joined', function(room, clientId) { 98 | console.log('Joined a room : ' + room + ' - my client id is: ' + clientId); 99 | isInitiator = false; 100 | if(restart == true ) { 101 | isInitiator = true; 102 | } 103 | }); 104 | 105 | socket.on('full', function(room, clientId) { 106 | //The idea is to create a new room for them 107 | var newRoom = prompt('Enter a new room to join: '); 108 | if(room != "default") { 109 | window.location = roomURL.innerText.replace(roomURL.innerText.split('/')[4], newRoom); 110 | } 111 | else { 112 | window.location = roomURL.innerText + newRoom; 113 | } 114 | }); 115 | 116 | socket.on('nowready', function() { 117 | //restartButton.disabled = false; 118 | createPeerConnection(isInitiator, iceServers, options); 119 | }); 120 | 121 | 122 | socket.on('logger', function(array) { 123 | console.log.apply(console, array); 124 | }); 125 | 126 | socket.on('message', function(message) { 127 | console.log('Client received message:' + message); 128 | signallingMessageCallback(message); 129 | }); 130 | 131 | socket.on('disconnect', function(room) { 132 | socket.emit('on_disconnect', room); 133 | }); 134 | 135 | socket.emit('got connected'); 136 | 137 | socket.emit('create or join', room); 138 | 139 | /* Send the message to Signalling Server */ 140 | function sendMessage(message) { 141 | console.log('Client sending message: ', message); 142 | socket.emit('message', message); 143 | } 144 | 145 | /*Updates URL on the page so users can open in a new tab for checking */ 146 | function updateRoomURL() { 147 | roomURL.innerHTML = '' + window.location.href + ''; 148 | console.log('Updated URL is: ' + url); 149 | } 150 | /* 151 | function clue(text) { 152 | console.log((window.performance.now / 1000).toFixed(3) + ': ' + text); 153 | } */ 154 | 155 | /* PeerConnection and DataChannel */ 156 | 157 | function signallingMessageCallback(message) { 158 | if(message.type === 'offer') { 159 | console.log('Got an offer, sending back an answer'); 160 | pC.setRemoteDescription(new RTCSessionDescription(message), function() { 161 | pC.createAnswer(onLocalSessionCreated, logError); 162 | }, logError); 163 | 164 | } 165 | else if(message.type === 'answer') { 166 | console.log('Got an answer'); 167 | pC.setRemoteDescription(new RTCSessionDescription(message), function(){}, logError); 168 | } 169 | else if(message.type === 'candidate') { 170 | console.log("Adding an ICE Candidate"); 171 | pC.addIceCandidate(new RTCIceCandidate({ 172 | sdpMLineIndex: message.sdpMLineIndex, 173 | candidate: message.candidate 174 | })); 175 | } 176 | else if(message === 'bye') { 177 | //Cleanup RTC Connection 178 | } 179 | } 180 | 181 | function createPeerConnection(isInitiator, iceServers, options) { 182 | console.log('Creating peer connection, initiator ' + isInitiator + ' Ice Servers: ' + iceServers); 183 | pC = new RTCPeerConnection(iceServers, options); 184 | 185 | // send any ice candidate to the other peer 186 | pC.onicecandidate = function(iceevent) { 187 | console.log('onicecandidate event fired, event: ' + iceevent); 188 | if(iceevent.candidate) { 189 | sendMessage({ 190 | type: 'candidate', 191 | label: iceevent.candidate.sdpMLineIndex, 192 | id: iceevent.candidate.sdpMid, 193 | candidate: iceevent.candidate.candidate 194 | }); 195 | } 196 | else { 197 | console.log('The candidates have got over'); 198 | } 199 | }; 200 | 201 | 202 | // if it's the initiator it needs to create the data channel 203 | if(isInitiator) { 204 | console.log('Creating the data channel'); 205 | dataChannel = pC.createDataChannel('fileChannel', {reliable: true}); 206 | dataChannel.binaryType = "arraybuffer"; 207 | onDataChannelCreated(dataChannel); 208 | 209 | console.log('Now creating an offer'); 210 | pC.createOffer(onLocalSessionCreated, logError, sdpOptions); 211 | } 212 | else { 213 | pC.ondatachannel = function(event) { 214 | console.log('ondatachannel: ' + event.channel); 215 | dataChannel = event.channel; 216 | onDataChannelCreated(dataChannel); 217 | }; 218 | } 219 | } 220 | 221 | function onLocalSessionCreated(descrip) { 222 | console.log('Local session create: ' + descrip); 223 | pC.setLocalDescription(descrip, function() { 224 | console.log('Sending the local description: ' + pC.localDescription); 225 | sendMessage(pC.localDescription); 226 | }, logError); 227 | } 228 | 229 | function onDataChannelCreated(dataChannel) { 230 | console.log('onDataChannelCreated : ' + dataChannel); 231 | 232 | dataChannel.onopen = function() { 233 | console.log('The data channel : '+ dataChannel + ' is OPEN'); 234 | sendButton.disabled = false; 235 | closeButton.disabled = false; 236 | restartButton.disabled = false; 237 | }; 238 | 239 | dataChannel.onmessage = onReceiveMessage; 240 | } 241 | 242 | function onReceiveMessage(event) { 243 | if(typeof event.data === 'string') { 244 | if(/^\d+$/.test(event.data) && !isNaN(event.data)) { 245 | size = parseInt(event.data); 246 | receiveBuffer = []; 247 | receivedSize = 0; 248 | console.log('Expecting a total of ' + size + ' bytes'); 249 | return; 250 | } 251 | else { 252 | name = event.data; 253 | console.log("Received name is " + name); 254 | return; 255 | } 256 | } 257 | console.log('Received message ' + event.data.byteLength); 258 | receiveBuffer.push(event.data); 259 | receivedSize += parseInt(event.data.byteLength); 260 | console.log('Received message ' + receivedSize + ' so far'); 261 | 262 | 263 | console.log(typeof(receivedSize) + ' and ' + typeof(size)); 264 | if(receivedSize === size) { 265 | console.log('Everything has been received'); 266 | var received = new window.Blob(receiveBuffer); 267 | receiveBuffer = []; 268 | readyForDownload(received); 269 | } 270 | } 271 | 272 | function handleMessage(event) { 273 | console.log('The received message is ' + event.data); 274 | //receiveTextArea.value = event.data; 275 | } 276 | 277 | 278 | function handleICEConnectionStateChange() { 279 | if(pC.iceConnectionState == 'disconnected') { 280 | console.log('The Client Disconnected'); 281 | } 282 | } 283 | 284 | function logError(error) { 285 | console.log(error.toString(), error); 286 | } 287 | 288 | 289 | 290 | function closeChannels() { 291 | console.log('Closing DataChannels'); 292 | dataChannel.close(); 293 | closeConnection(); 294 | } 295 | 296 | function restartConnection() { 297 | //restart = true; 298 | handleUnload(); 299 | console.log('Restarting the connection'); 300 | socket.emit('create or join', room); 301 | //isInitiator = true; 302 | createPeerConnection(isInitiator, iceServers, options); 303 | 304 | } 305 | 306 | function closeConnection() { 307 | console.log('Closing the PeerConnection'); 308 | pC.close(); 309 | } 310 | 311 | /* Data Sending and UI */ 312 | function sendData() { 313 | 314 | //Split datachannel message into proper sized chunks, getting the number of chunks 315 | var chunkLen; 316 | if(webrtcDetectedBrowser === 'firefox') { 317 | chunkLen = 16000; //Used to be 64kb 318 | } 319 | else { //Assuming it's chrome 320 | chunkLen = 16000; 321 | } 322 | var file = theFile; 323 | var len = file.size; 324 | var n = len / chunkLen | 0; 325 | var blob; 326 | 327 | //Inform the file size to the recepient 328 | console.log('The filesize is : ' + len + ' bytes'); 329 | dataChannel.send(len); 330 | console.log('The file name is : ' + file.name); 331 | dataChannel.send(file.name); 332 | 333 | var sliceFile = function(offset) { 334 | var reader = new FileReader(); 335 | reader.onload = (function() { 336 | return function(e) { 337 | dataChannel.send(e.target.result); 338 | console.log("Sent chunk of size " + e.target.result.byteLength); 339 | if( len > offset + e.target.result.byteLength) { 340 | window.setTimeout(sliceFile, 0, offset + chunkLen); 341 | } 342 | }; 343 | })(file); 344 | var slice = file.slice(offset, offset + chunkLen); 345 | reader.readAsArrayBuffer(slice) 346 | }; 347 | sliceFile(0); 348 | } 349 | 350 | 351 | function readyForDownload(data) { 352 | var fileURL = URL.createObjectURL(data); 353 | var text = 'Click to download ' + name + ' of size ' + size + ' bytes'; 354 | downloadLink.innerHTML = text; 355 | downloadLink.download = name; 356 | downloadLink.href = fileURL; 357 | } 358 | 359 | 360 | function getFile() { 361 | theFile = this.files[0]; 362 | console.log('File got is: ' + theFile.name + ' with size: ' + theFile.size + ' with type: ' + theFile.type); 363 | } 364 | 365 | function handleUnload() { 366 | console.log('Leaving the room'); 367 | socket.emit('leave', room); 368 | console.log('Closing Data Channel'); 369 | dataChannel.close(); 370 | console.log('Closing peer connection'); 371 | pC.close(); 372 | } 373 | 374 | } 375 | -------------------------------------------------------------------------------- /static/search.js: -------------------------------------------------------------------------------- 1 | function readyFunction(current_user_hostel) { 2 | 3 | var url = $SCRIPT_ROOT + '/results'; 4 | 5 | $("input#searchbox").on('input',function () { 6 | searchBox = $("input#searchbox"); 7 | if(searchBox.val() != " " && searchBox.val() != "" && searchBox.val() != " " && searchBox.val() != " " && searchBox.val() != " " && searchBox.val() != " ") { 8 | console.log("SearchBox value is: " + searchBox.val()); 9 | var searchData = '{"query":"' + searchBox.val() + '"}'; 10 | var JSONsearchData = JSON.parse(searchData); 11 | 12 | $.ajax({ 13 | type: "POST", 14 | url: url, 15 | async: true, 16 | processData: false, 17 | contentType: 'application/json;charset=UTF-8', 18 | dataType: 'json', 19 | data: JSON.stringify(JSONsearchData), 20 | success: function(data) { 21 | var suggestList = $("ul.suggest-list"); 22 | suggestList.empty(); 23 | console.log("And it's back"); 24 | for(var i = 0; i < data.result.length; i++) { 25 | suggestList.append(''); 26 | } 27 | } 28 | }); 29 | } 30 | }); 31 | } -------------------------------------------------------------------------------- /templates/add.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block head %} 3 | 5 | 7 | 11 | 13 | Stuff - CampFile 14 | {% endblock head %} 15 | {% block content %} 16 |
17 |
18 |
19 |
20 | 21 | 22 | Movies 23 | 24 | TV Shows 25 | 26 | Music 27 | 28 | Other 29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 | {% endblock content %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block head %} 6 | Base Page of Download Index 7 | {% endblock head %} 8 | 9 | 10 |
11 | 12 | 13 | {% block content %}{% endblock %} 14 | 15 | 16 | {% if error %} 17 |

Error:{{ error }}

18 | {% endif %} 19 | 20 | 21 | {% for message in get_flashed_messages() %} 22 | {{ message }} 23 | {% endfor %} 24 |
25 | 26 | -------------------------------------------------------------------------------- /templates/filetransfer.html: -------------------------------------------------------------------------------- 1 | {% block head %} 2 | 4 | 6 | 10 | 11 | File Transfer 12 | {% endblock head %} 13 | {% block content %} 14 | 15 |
16 | Room URL: ... 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 | 25 | 26 | 27 |
28 | 29 | 35 | 36 | 38 | 39 | 45 | {% endblock content %} -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block head %} 3 | Campfile Home 4 | 6 | 8 | 9 | 10 | {% endblock head %} 11 | {% block content %} 12 |
13 | {% for file in files.items %} 14 |
15 | {{ file.name }} -> {{file.ownerhostel }} -> {{ file.size }} 16 |
17 | {% endfor %} 18 | {% if files.has_prev %} 19 | 20 | Previous Page 21 | 22 | {% else %} 23 | Previous Page 24 | {% endif %} 25 | {% if files.has_next %} 26 | 27 | Next Page 28 | 29 | {% else %} 30 | Next Page 31 | {% endif %} 32 |
33 | 34 | StartRequest 35 | 36 | {% endblock content %} 37 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block head %} 3 | Login - CampFile 4 | {% endblock head %} 5 | {% block content %} 6 |

Please Login

7 |
8 |
9 | {{ form.csrf_token }} 10 | 11 |

12 | {{ form.username(placeholder="username") }} 13 | 14 | {% if form.username.errors %} 15 | {% for error in form.username.errors %} 16 | {{ error }} 17 | {% endfor %} 18 | {% endif %} 19 | 20 |

21 |

22 | {{ form.password(placeholder="password") }} 23 | 24 | {% if form.password.errors %} 25 | {% for error in form.password.errors %} 26 | {{ error }} 27 | {% endfor %} 28 | {% endif %} 29 | 30 |

31 | 32 |
33 | {% endblock content %} -------------------------------------------------------------------------------- /templates/newuser.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 |

Register

5 |
6 |
7 | {{ form.csrf_token }} 8 |
9 | {{ form.username(placeholder="username") }} 10 | 11 | {% if form.username.errors %} 12 | {% for error in for.username.errors %} 13 | {{ error }} 14 | {% endfor %} 15 | {% endif %} 16 | 17 |
18 |
19 | {{ form.firstname(placeholder="Firstname") }} 20 | 21 | {% if form.firstname.errors %} 22 | {% for error in for.firstname.errors %} 23 | {{ error }} 24 | {% endfor %} 25 | {% endif %} 26 | 27 |
28 |
29 | {{ form.lastname(placeholder="Lastname") }} 30 | 31 | {% if form.lastname.errors %} 32 | {% for error in for.lastname.errors %} 33 | {{ error }} 34 | {% endfor %} 35 | {% endif %} 36 | 37 |
38 |
39 | {{ form.hostel(placeholder="Hostel") }} 40 | 41 | {% if form.hostel.errors %} 42 | {% for error in for.hostel.errors %} 43 | {{ error }} 44 | {% endfor %} 45 | {% endif %} 46 | 47 |
48 |
49 | {{ form.room(placeholder="Room Number") }} 50 | 51 | {% if form.room.errors %} 52 | {% for error in for.room.errors %} 53 | {{ error }} 54 | {% endfor %} 55 | {% endif %} 56 | 57 |
58 |
59 | {{ form.year(placeholder="Year") }} 60 | 61 | {% if form.year.errors %} 62 | {% for error in for.year.errors %} 63 | {{ error }} 64 | {% endfor %} 65 | {% endif %} 66 | 67 |
68 |
69 | {{ form.password(placeholder="password") }} 70 | 71 | {% if form.password.errors %} 72 | {% for error in for.password.errors %} 73 | {{ error }} 74 | {% endfor %} 75 | {% endif %} 76 | 77 |
78 |
79 | {{ form.confirm(placeholder="confirm") }} 80 | 81 | {% if form.confirm.errors %} 82 | {% for error in form.confirm.errors %} 83 | {{ error }} 84 | {% endfor %} 85 | {% endif %} 86 | 87 |
88 | 89 |
90 | {% endblock %} -------------------------------------------------------------------------------- /templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block head %} 3 | 4 | 6 | 8 | 12 | 14 | 19 | Search - CampFile 20 | {% endblock head %} 21 | {% block content %} 22 |
23 | {{ form.csrf_token }} 24 |
25 | 26 | {{ form.search(id="searchbox") }} 27 | 28 | {% if form.search.errors %} 29 | {% for error in for.search.errors %} 30 | {{ error }} 31 | {% endfor %} 32 | {% endif %} 33 | 34 | 35 |
36 |
37 |
    38 | 39 |
40 |
41 | {% endblock content %} -------------------------------------------------------------------------------- /templates/sse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 17 | 18 | 19 | Just a DEMO 20 |
    21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | import unittest 3 | 4 | #The first word of the method has to be test 5 | class FlaskTestCase(unittest.TestCase): 6 | #Ensure Flask was set up correctly 7 | def test_index(self): 8 | tester = app.test_client(self) 9 | response = tester.get('/login', content_type='html/text') 10 | self.assertEqual(response.status_code, 200) 11 | 12 | # Ensure logout behaves correctly 13 | def test_logout(self): 14 | tester = app.test_client() 15 | tester.post('/login', data=dict(username="admin", password="admin"), follow_redirects=True) 16 | response = tester.get('/logout', follow_redirects=True) 17 | self.assertIn(b'You have been logged out', response.data) 18 | 19 | #Ensure main page needs user login 20 | def test_main_route_requires_login(self): 21 | tester = app.test_client() 22 | response = tester.get('/', follow_redirects=True) 23 | self.assertIn(b'Please login to view this page', response.data) 24 | 25 | 26 | #Ensure the uploading is there on the main page 27 | def test_uploading_exists_on_main_page(self): 28 | tester = app.test_client() 29 | response = tester.post( 30 | '/login', 31 | data=dict(username="admin", password="admin"), 32 | follow_redirects=True 33 | ) 34 | self.assertIn(b'Pick a file to add', response.data) 35 | 36 | if __name__ == '__main__': 37 | unittest.main() --------------------------------------------------------------------------------