'
+ with app.app_context():
+ for admin in SERVER_ADMINS:
+ html_body = '
Hello %s,' \
+ ' Here is the upload report for last week: %s' \
+ ' There are %s space used and %s MB of space left.' \
+ % (admin["name"], reports_by_user_table, rounded_usage, usage_info[0])
+
+ msg = Message(subject,
+ html=html_body,
+ recipients=[admin['email']])
+ mail.send(msg)
+
+
+# @app.route('/storage_full/')
+def if_storage_full():
+ usage_info = get_usage_info()
+ if usage_info[0] > THRESHOLD:
+ subject = 'Storage is Nearly Full'
+ rounded_usage = round(usage_info[0], 4)
+ with app.app_context():
+ for admin in SERVER_ADMINS:
+ html_body = '
Hello %s,' \
+ ' The logfiles uploaded took %s of the server\'s total disk space.' \
+ 'Oldest files will be deleted to free space.
' % (admin["name"], rounded_usage)
+
+ msg = Message(subject,
+ html=html_body,
+ recipients=[admin['email']])
+ mail.send(msg)
+ free_storage()
+
+ # with smtplib.SMTP('mail.wrs.com', 587) as smtp:
+ # # smtp.ehlo()
+ # smtp.starttls()
+ # smtp.ehlo()
+ #
+ # # smtp.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
+ # smtp.login('Nathan.Chen@windriver.com', 'MyPassword')
+ #
+ # subject = 'Storage is full'
+ # body = 'Do something'
+ #
+ # msg = f'Subject: {subject}\n\n{body}'
+ #
+ # smtp.sendmail('Nathan.Chen@windriver.com', 'wrcollecttesting1@gmail.com', msg)
+
+
+scheduler = BackgroundScheduler()
+scheduler.add_job(func=delete_old_files, trigger="cron", day='1')
+scheduler.add_job(func=check_launchpads, trigger="cron", day_of_week='mon-fri')
+scheduler.add_job(func=if_storage_full, trigger="cron", minute='00')
+scheduler.add_job(func=send_weekly_reports, trigger="cron", day_of_week='mon')
+scheduler.start()
+
+atexit.register(lambda: scheduler.shutdown())
+
+
+# @app.route('/usage/')
+def get_usage_info():
+ # return str(shutil.disk_usage(app.config['BASE_DIR']))
+ # return str(shutil.disk_usage(os.path.join(app.config['BASE_DIR'], 'files')).used)
+ # return str(shutil.disk_usage(os.path.join(app.config['BASE_DIR'], 'files')))
+ disk_usage_info = shutil.disk_usage(os.path.join(app.config['BASE_DIR'], 'files'))
+ usage_perc = disk_usage_info.used/disk_usage_info.total
+ return [usage_perc, disk_usage_info.free]
+ # return str(usage_perc)
+
+
+def is_allowed(filename):
+ return '.' in filename and (filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
+ or filename.rsplit('.', 2)[1] == 'tar') \
+ and filename.rsplit('.', 1)[1] not in DISALLOWED_EXTENSIONS
+
+
+def get_size(fobj):
+ if fobj.content_length:
+ return fobj.content_length
+
+ try:
+ pos = fobj.tell()
+ fobj.seek(0, 2) # seek to end
+ size = fobj.tell()
+ fobj.seek(pos) # back to original position
+ return size
+ except (AttributeError, IOError):
+ pass
+
+ # in-memory file object that doesn't support seeking or tell
+ return 0 # assume small enough
+
+
+def connect():
+ return pymysql.connect(host='db',
+ user='root',
+ password='Wind2019',
+ db='collect',
+ charset='utf8mb4',
+ cursorclass=pymysql.cursors.DictCursor,
+ autocommit=True)
+
+
+def confirmation_required(desc_fn):
+ def inner(f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ if request.args.get('confirm') != '1':
+ desc = desc_fn()
+ return redirect(url_for('confirm', desc=desc, action_url=quote(request.url)))
+ return f(*args, **kwargs)
+
+ return wrapper
+
+ return inner
+
+
+def get_launchpad_info(lp):
+ lp_id = lp
+ if isinstance(lp, dict):
+ lp_id = int(lp['launchpad_id'])
+ cachedir = os.path.join(app.config['BASE_DIR'], '.launchpadlib/cache/')
+ launchpad = Launchpad.login_anonymously('just testing', 'production', cachedir, version='devel')
+ try:
+ bug_one = launchpad.bugs[lp_id]
+ # return str(type(bug_one))
+ # return bug_one.bug_tasks.entries[0]['title']
+ is_starlingx = any(entry['bug_target_name'] == 'starlingx' for entry in bug_one.bug_tasks.entries)
+ if not is_starlingx:
+ return 'You need to choose a valid StarlingX Launchpad'
+ closed = all(entry['date_closed'] is not None for entry in bug_one.bug_tasks.entries)
+ return [bug_one.title, closed]
+ except KeyError:
+ return 'Launchpad bug id does not exist'
+
+
+@app.errorhandler(413)
+@app.errorhandler(RequestEntityTooLarge)
+def error413(e):
+ # flash(u'Error: File size exceeds the 16GB limit')
+ # return render_template('index.html'), 413
+ # return render_template('413.html'), 413
+ return 'File Too Large', 413
+
+
+@app.errorhandler(401)
+def error401(e):
+ flash(u'Error: You are not logged in')
+ return redirect(url_for('login', next=oid.get_next_url()))
+
+
+@app.route('/confirm')
+def confirm():
+ desc = request.args['desc']
+ action_url = unquote(request.args['action_url'])
+
+ return render_template('_confirm.html', desc=desc, action_url=action_url)
+
+
+def confirm_delete_profile():
+ return "Are you sure you want to delete your profile? All your files will be removed."
+
+
+def confirm_delete_file():
+ return "Are you sure you want to delete this file?"
+
+
+@app.before_request
+def before_request():
+ g.user = None
+ g.connection = connect()
+ if 'openid' in session:
+ with g.connection.cursor() as cursor:
+ sql = "select * from openid_users where openid = %s;"
+ cursor.execute(sql, (session['openid'],))
+ g.user = cursor.fetchone()
+ cursor.close()
+
+
+@app.after_request
+def after_request(response):
+ return response
+
+
+@app.route('/')
+def index():
+ return render_template('index.html')
+
+
+@app.route('/login/', methods=['GET', 'POST'])
+@oid.loginhandler
+def login():
+ if g.user is not None:
+ return redirect(oid.get_next_url())
+ if request.method == 'POST':
+ openid = "https://launchpad.net/~" + request.form.get('openid')
+ if openid:
+ pape_req = pape.Request([])
+ return oid.try_login(openid, ask_for=['email', 'nickname'],
+ ask_for_optional=['fullname'],
+ extensions=[pape_req])
+ return render_template('login.html', next=oid.get_next_url(),
+ error=oid.fetch_error())
+
+
+@oid.after_login
+def create_or_login(resp):
+ """This is called when login with OpenID succeeded and it's not
+ necessary to figure out if this is the users's first login or not.
+ This function has to redirect otherwise the user will be presented
+ with a terrible URL which we certainly don't want.
+ """
+ session['openid'] = resp.identity_url
+ if 'pape' in resp.extensions:
+ pape_resp = resp.extensions['pape']
+ session['auth_time'] = pape_resp.auth_time
+ with g.connection.cursor() as cursor:
+ sql = "select * from openid_users where openid = %s;"
+ cursor.execute(sql, (resp.identity_url,))
+ user = cursor.fetchone()
+ cursor.close()
+ # user = User.query.filter_by(openid=resp.identity_url).first()
+ if user is not None:
+ flash(u'Successfully signed in')
+ g.user = user
+ return redirect(oid.get_next_url())
+ return redirect(url_for('create_profile', next=oid.get_next_url(),
+ name=resp.fullname or resp.nickname,
+ email=resp.email))
+
+
+@app.route('/create-profile/', methods=['GET', 'POST'])
+def create_profile():
+ """If this is the user's first login, the create_or_login function
+ will redirect here so that the user can set up his profile.
+ """
+ if g.user is not None or 'openid' not in session:
+ return redirect(url_for('index'))
+ if request.method == 'POST':
+ name = request.form['name']
+ email = request.form['email']
+ if not name:
+ flash(u'Error: you have to provide a name')
+ elif '@' not in email:
+ flash(u'Error: you have to enter a valid email address')
+ else:
+ flash(u'Profile successfully created')
+ with g.connection.cursor() as cursor:
+ sql = "INSERT INTO openid_users (name, email, openid) VALUES (%s, %s, %s);"
+ cursor.execute(sql, (name, email, session['openid'],))
+ sql = "SELECT id FROM openid_users WHERE openid = %s;"
+ cursor.execute(sql, (session['openid'],))
+ user_id = cursor.fetchone()['id']
+ _dir = os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(user_id))
+ os.mkdir(_dir)
+ cursor.close()
+ return redirect(oid.get_next_url())
+ return render_template('create_profile.html', next_url=oid.get_next_url())
+
+
+@app.route('/profile/', methods=['GET', 'POST'])
+def edit_profile():
+ """Updates a profile"""
+ if g.user is None:
+ abort(401)
+ # form = dict(name=g.user.name, email=g.user.email)
+ # g_user_name = g.user.name
+ # g_user_email = g.user.email
+ form = {'name': g.user['name'], 'email': g.user['email']}
+ # form = {'name': 1, 'email': 1}
+ # form['name'] = g.user.name
+ # form['email'] = g.user.email
+ if request.method == 'POST':
+ if 'delete' in request.form:
+ with g.connection.cursor() as cursor:
+ sql = "DELETE FROM openid_users WHERE openid=%s;"
+ cursor.execute(sql, (session['openid'],))
+ cursor.close()
+ shutil.rmtree(os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(g.user['id'])))
+ return redirect(oid.get_next_url())
+ session['openid'] = None
+ flash(u'Profile deleted')
+ return redirect(url_for('index'))
+ form['name'] = request.form['name']
+ form['email'] = request.form['email']
+ if not form['name']:
+ flash(u'Error: you have to provide a name')
+ elif '@' not in form['email']:
+ flash(u'Error: you have to enter a valid email address')
+ else:
+ flash(u'Profile successfully created')
+ g.user['name'] = form['name']
+ g.user['email'] = form['email']
+ with g.connection.cursor() as cursor:
+ sql = "UPDATE openid_users SET name = %s, email = %s WHERE id = %s;"
+ cursor.execute(sql, (g.user['name'], g.user['email'], g.user['id'],))
+ cursor.close()
+ return redirect(oid.get_next_url())
+ # db_session.commit()
+ # return redirect(url_for('edit_profile'))
+ return render_template('edit_profile.html', form=form)
+
+
+@app.route('/logout/')
+def logout():
+ session.pop('openid', None)
+ flash(u'You have been signed out')
+ return redirect(oid.get_next_url())
+
+
+@app.route('/check_launchpad/', methods=['GET', 'POST'])
+def check_launchpad(launchpad_id):
+ with g.connection.cursor() as cursor:
+ sql = "SELECT * FROM launchpads WHERE id = %s;"
+ cursor.execute(sql, (launchpad_id,))
+ launchpad_info = cursor.fetchone()
+ if launchpad_info:
+ launchpad_title = launchpad_info["title"]
+ else:
+ try:
+ launchpad_info = get_launchpad_info(int(launchpad_id))
+ except ValueError:
+ res = make_response("Error: Launchpad bug id does not exist", 400)
+ return res
+ if launchpad_info == 'Launchpad bug id does not exist':
+ res = make_response("Error: Launchpad bug id does not exist", 400)
+ return res
+ elif launchpad_info == 'You need to choose a valid StarlingX Launchpad':
+ res = make_response("Error: You need to choose a valid StarlingX Launchpad", 400)
+ return res
+ elif launchpad_info[1] is True:
+ res = make_response("Error: Launchpad bug is closed", 400)
+ return res
+ sql = "INSERT INTO launchpads (id, title) VALUES (%s, %s);"
+ cursor.execute(sql, (launchpad_id, launchpad_info[0],))
+ launchpad_title = launchpad_info[0]
+ res = make_response(launchpad_title, 200)
+ return res
+
+
+@app.route('/upload/', methods=['GET', 'POST'])
+def upload():
+ try:
+ if g.user is None:
+ abort(401)
+ if request.method == 'POST':
+ launchpad_id = request.args.get('launchpad_id')
+ if launchpad_id == '':
+ res = make_response(jsonify({"message": "Error: you did not supply a valid Launchpad ID"}), 400)
+ return res
+ launchpad_info = 0
+
+ # with g.connection.cursor() as cursor:
+ # sql = "SELECT id FROM launchpads WHERE id = %s;"
+ # cursor.execute(sql, (launchpad_id,))
+ # if not cursor.fetchone():
+ # try:
+ # launchpad_info = get_launchpad_info(int(launchpad_id))
+ # except ValueError:
+ # res = make_response(jsonify({"message": "Error: Launchpad bug id does not exist"}), 400)
+ # return res
+ # if launchpad_info == 'Launchpad bug id does not exist' or launchpad_info[1] is True:
+ # res = make_response(jsonify({"message": "Error: Launchpad bug id does not exist"}), 400)
+ # return res
+ # sql = "INSERT INTO launchpads (id, title) VALUES (%s, %s);"
+ # cursor.execute(sql, (launchpad_id, launchpad_info[0],))
+
+ # file_list = request.files.getlist('file')
+ # file_list = request.files['file']
+ # file_list = request.files.get('file[]')
+ #
+ # res = make_response(jsonify({"message": str(file_list)}), 200)
+ # return res
+
+ # file_size = get_size(f)
+ # if file_size > MAX_CONTENT_LENGTH:
+ # flash(u'Error: File size exceeds the 10GB limit')
+ # return redirect(oid.get_next_url())
+ # for f in file_list:
+ f = request.files['file']
+ if f and is_allowed(f.filename):
+ _launchpad_dir = os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(g.user['id']), launchpad_id)
+ conflict = request.args.get('conflict')
+ final_filename = secure_filename(f.filename)
+ if conflict == '1':
+ if final_filename.rsplit('.', 2)[1] == 'tar' and len(final_filename.rsplit('.', 2)) == 3:
+ tail = 1
+ while True:
+ new_filename = final_filename.rsplit('.', 2)[0] + "_" + str(tail) + '.' \
+ + final_filename.rsplit('.', 2)[1] + '.' + final_filename.rsplit('.', 2)[2]
+ with g.connection.cursor() as cursor:
+ file_name_sql = "SELECT id FROM files WHERE launchpad_id=%s AND name=%s AND user_id=%s;"
+ cursor.execute(file_name_sql, (launchpad_id, new_filename, g.user['id']))
+ if cursor.fetchone():
+ tail = tail + 1
+ else:
+ break
+ else:
+ tail = 1
+ while True:
+ new_filename = final_filename.rsplit('.', 1)[0] + "_" + str(tail) + '.' \
+ + final_filename.rsplit('.', 1)[1]
+ with g.connection.cursor() as cursor:
+ file_name_sql = "SELECT id FROM files WHERE launchpad_id=%s AND name=%s AND user_id=%s;"
+ cursor.execute(file_name_sql, (launchpad_id, new_filename, g.user['id']))
+ if cursor.fetchone():
+ tail = tail + 1
+ else:
+ break
+ final_filename = new_filename
+ if not os.path.isdir(_launchpad_dir):
+ os.mkdir(_launchpad_dir)
+ _full_dir = os.path.join(_launchpad_dir, final_filename)
+ f.save(_full_dir)
+ file_size = os.stat(_full_dir).st_size
+ # mimetype = f.mimetype
+ # flash(mimetype)
+ mime = magic.Magic(mime=True)
+ mimetype = mime.from_file(_full_dir)
+ if mimetype.startswith('image/', 0) or mimetype.startswith('video/', 0)\
+ or mimetype in DISALLOWED_MIME_TYPES:
+ os.remove(_full_dir)
+ res = make_response(jsonify({
+ "message": "Error: you did not supply a valid file in your request"}), 400)
+ return res
+ if mimetype in ['application/x-gzip', 'application/x-tar']:
+ tar = tarfile.open(_full_dir)
+ if not any((any(x in y for x in TARGETED_TAR_CONTENTS) for y in tar.getnames())):
+ res = make_response(jsonify({
+ "message": "Error: you did not supply a valid collect file in your request"}), 400)
+ logging.info(tar.getnames())
+ return res
+ if conflict == '0':
+ with g.connection.cursor() as cursor:
+ sql = "UPDATE files SET modified_date = %s, file_size = %s " \
+ "WHERE user_id = %s AND name = %s AND launchpad_id = %s;"
+ cursor.execute(
+ sql, (datetime.now(), file_size, g.user['id'], final_filename, launchpad_id))
+ cursor.close()
+ logging.info('User#{} re-uploaded file {} under launchpad bug#{}'.
+ format(g.user['id'], final_filename, launchpad_id))
+ else:
+ with g.connection.cursor() as cursor:
+ sql = "INSERT INTO files (name, user_id, launchpad_id, modified_date, file_size)" \
+ "VALUES (%s, %s, %s, %s, %s);"
+ cursor.execute(sql, (final_filename, g.user['id'], launchpad_id, datetime.now(),
+ file_size,))
+ cursor.close()
+ logging.info('User#{} uploaded file {} under launchpad bug#{}'.
+ format(g.user['id'], final_filename, launchpad_id))
+ else:
+ logging.error('User#{} tried to upload a file with invalid format'.format(g.user['id']))
+ res = make_response(jsonify({"message": "Error: you did not supply a valid file in your request"}), 400)
+ return res
+ # except RequestEntityTooLarge:
+ # flash(u'Error: File size exceeds the 16GB limit')
+ # return redirect(oid.get_next_url())
+ res = make_response(jsonify({"message": "file uploaded successfully: {}".format(f.filename)}), 200)
+ return res
+ return render_template('upload.html')
+ except RequestEntityTooLarge:
+ flash(u'Error: File size exceeds the 10GB limit')
+ return redirect(oid.get_next_url())
+
+
+@app.route('/user_files/', methods=['GET', 'POST'])
+def list_user_files():
+ """Updates a profile"""
+ if g.user is None:
+ abort(401)
+ user_files = []
+ if request.method == 'GET':
+ with g.connection.cursor() as cursor:
+ search = request.args.get('search')
+ if search:
+ sql = "SELECT f.*, l.title FROM files f JOIN launchpads l ON f.launchpad_id = l.id " \
+ "WHERE (launchpad_id = '{}' OR title LIKE '%{}%') AND user_id = {} " \
+ "ORDER BY launchpad_id DESC, f.name;".format(search, search, g.user['id'])
+ # sql = "SELECT * FROM launchpads WHERE id IN (SELECT DISTINCT launchpad_id FROM files WHERE user_id = %s);"
+ cursor.execute(sql,)
+ else:
+ sql = "SELECT f.*, l.title FROM files f JOIN launchpads l ON f.launchpad_id = l.id " \
+ "WHERE user_id = %s ORDER BY launchpad_id DESC;"
+ cursor.execute(sql, (g.user['id'],))
+ user_files = cursor.fetchall()
+ cursor.close()
+ return render_template('user_files.html', user_files=user_files)
+
+
+@app.route('/public_files/', methods=['GET', 'POST'])
+def list_public_files():
+ """Updates a profile"""
+ if g.user is None:
+ abort(401)
+ files = []
+ if request.method == 'GET':
+ with g.connection.cursor() as cursor:
+ sql = "SELECT f.*, l.title, f.user_id, u.name AS user_name FROM files f JOIN launchpads l " \
+ "ON f.launchpad_id = l.id JOIN openid_users u ON f.user_id = u.id;"
+ cursor.execute(sql)
+ files = cursor.fetchall()
+ cursor.close()
+ # if files:
+ # files = list(map(lambda f: f.update({'editable': (f['user_id'] == g.user)})))
+ return render_template('public_files.html', public_files=files)
+
+
+@app.route('/launchpads/', methods=['GET', 'POST'])
+def list_all_launchpads():
+ """Updates a profile"""
+ if g.user is None:
+ abort(401)
+ if request.method == 'GET':
+ with g.connection.cursor() as cursor:
+ search = request.args.get('search')
+ if search:
+ sql = "SELECT * FROM launchpads WHERE (id = '{}' OR title LIKE '%{}%') " \
+ "AND id IN (SELECT DISTINCT launchpad_id FROM files) ORDER BY id DESC;".format(search, search)
+ # sql = "SELECT * FROM launchpads WHERE id IN (SELECT DISTINCT launchpad_id FROM files WHERE user_id = %s);"
+ cursor.execute(sql,)
+ else:
+ sql = "SELECT * FROM launchpads WHERE id IN (SELECT DISTINCT launchpad_id FROM files) ORDER BY id DESC;"
+ cursor.execute(sql,)
+ user_launchpads = cursor.fetchall()
+ cursor.close()
+ return render_template('launchpads.html', user_launchpads=user_launchpads)
+
+
+@app.route('/launchpad/', methods=['GET', 'POST'])
+def list_files_under_a_launchpad(launchpad_id):
+ """Updates a profile"""
+ if g.user is None:
+ abort(401)
+ if request.method == 'GET':
+ with g.connection.cursor() as cursor:
+ sql = "SELECT f.*, u.name AS user_name FROM files f " \
+ "JOIN openid_users u ON f.user_id = u.id WHERE launchpad_id = %s;"
+ cursor.execute(sql, (launchpad_id,))
+ launchpad_files = cursor.fetchall()
+ sql = "SELECT * FROM launchpads WHERE id = %s;"
+ cursor.execute(sql, (launchpad_id,))
+ launchpad_info = cursor.fetchone()
+ cursor.close()
+ return render_template('launchpad.html', launchpad_files=launchpad_files, launchpad_info=launchpad_info)
+ # return render_template('user_files.html', user_launchpads=user_launchpads)
+
+
+@app.route('/edit_file/', methods=['GET', 'POST'])
+def edit_file(file_id):
+ """Updates a profile"""
+ if g.user is None:
+ abort(401)
+ with g.connection.cursor() as cursor:
+ sql = "SELECT * FROM files WHERE id = %s;"
+ cursor.execute(sql, (file_id,))
+ user_files = cursor.fetchone()
+ form = {'name': user_files['name'], 'launchpad_id': user_files['launchpad_id']}
+ old_form = form.copy()
+ cursor.close()
+ if request.method == 'POST':
+ form['name'] = request.form['name']
+ form['launchpad_id'] = request.form['launchpad_id']
+ if not form['name']:
+ flash(u'Error: you have to provide a name')
+ else:
+ # _dir = os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(g.user['id']))
+ # flash(os.path.join(_dir, old_form['launchpad_id'], old_form['name']))
+ if old_form['name'] != form['name'] or old_form['launchpad_id'] != int(form['launchpad_id']):
+ _dir = os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(g.user['id']))
+ _new_dir = os.path.join(_dir, str(form['launchpad_id']))
+ if old_form['launchpad_id'] != form['launchpad_id']:
+ with g.connection.cursor() as cursor:
+ sql = "SELECT * FROM launchpads WHERE id = %s;"
+ cursor.execute(sql, (form['launchpad_id'],))
+ launchpad_info = cursor.fetchone()
+ if not launchpad_info:
+ try:
+ launchpad_info = get_launchpad_info(int(form['launchpad_id']))
+ except ValueError:
+ res = make_response("Error: Launchpad bug id does not exist", 400)
+ return res
+ if launchpad_info == 'Launchpad bug id does not exist':
+ flash(u'Error: Launchpad bug id does not exist')
+ return redirect(oid.get_next_url())
+ elif launchpad_info == 'You need to choose a valid StarlingX Launchpad':
+ flash(u'Error: You need to choose a valid StarlingX Launchpad')
+ return redirect(oid.get_next_url())
+ elif launchpad_info[1] is True:
+ flash(u'Error: Launchpad bug is closed')
+ return redirect(oid.get_next_url())
+ else:
+ sql = "INSERT INTO launchpads (id, title) VALUES (%s, %s);"
+ cursor.execute(sql, (form['launchpad_id'], launchpad_info[0],))
+ cursor.close()
+ if not os.path.isdir(_new_dir):
+ os.mkdir(_new_dir)
+ os.rename(os.path.join(_dir, str(old_form['launchpad_id']), old_form['name']),
+ os.path.join(_dir, str(form['launchpad_id']), form['name']))
+ if old_form['name'] == form['name']:
+ logging.info('User#{} changed file {}/{} to {}/{}'.
+ format(g.user['id'], old_form['launchpad_id'],
+ old_form['name'], form['launchpad_id'], form['name']))
+ with g.connection.cursor() as cursor:
+ sql = "UPDATE files SET name = %s, launchpad_id = %s, modified_date = %s WHERE id = %s;"
+ cursor.execute(sql, (form['name'], form['launchpad_id'], datetime.now(), file_id,))
+ cursor.close()
+ flash(u'File information successfully updated')
+ return redirect(url_for('edit_file', file_id=file_id, form=form))
+ return render_template('edit_file.html', file_id=file_id, form=form)
+
+
+@app.route('/delete_file', methods=['GET', 'POST'])
+# @confirmation_required(confirm_delete_file)
+def delete_file():
+ """Updates a profile"""
+ if g.user is None:
+ abort(401)
+ file_id = request.form['id']
+ with g.connection.cursor() as cursor:
+ file_name_sql = "SELECT name, launchpad_id FROM files WHERE id=%s;"
+ cursor.execute(file_name_sql, (file_id,))
+ file_info = cursor.fetchone()
+ os.remove(os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(g.user['id']), str(file_info['launchpad_id']),
+ file_info['name']))
+
+ sql = "DELETE FROM files WHERE id=%s;"
+ cursor.execute(sql, (file_id,))
+ cursor.close()
+ logging.info('User#{} deleted file {} under launchpad bug#{}'.
+ format(g.user['id'], secure_filename(file_info['name']), file_info['launchpad_id']))
+ flash(u'File deleted')
+ return redirect(url_for('list_user_files'))
+
+
+@app.route('/download_file/', methods=['GET', 'POST'])
+def download_file(file_id):
+ with g.connection.cursor() as cursor:
+ file_name_sql = "SELECT name, launchpad_id, user_id FROM files WHERE id=%s;"
+ cursor.execute(file_name_sql, (file_id,))
+ file_info = cursor.fetchone()
+ download_link = os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(file_info['user_id']),
+ str(file_info['launchpad_id']), file_info['name'])
+ cursor.close()
+ return send_file(download_link, attachment_filename=file_info['name'], as_attachment=True, cache_timeout=0)
+
+
+@app.route('/download_launchpad/', methods=['GET', 'POST'])
+def download_launchpad(launchpad_id):
+ zipf = zipfile.ZipFile('{}.zip'.format(launchpad_id), 'w', zipfile.ZIP_DEFLATED)
+ with g.connection.cursor() as cursor:
+ launchpad_file_sql = "SELECT f.name, f.user_id, f.launchpad_id, u.name AS uploader FROM files f " \
+ "JOIN openid_users u ON f.user_id = u.id WHERE launchpad_id=%s;"
+ cursor.execute(launchpad_file_sql, (launchpad_id,))
+ files = cursor.fetchall()
+ for file in files:
+ zipf.write(os.path.join(app.config['BASE_DIR'], 'files/{}/'.format(file['user_id']),
+ str(file['launchpad_id']), file['name']),
+ os.path.join(file['uploader'], file['name']))
+ cursor.close()
+ zipf.close()
+
+ @after_this_request
+ def remove_file(response):
+ try:
+ file_path = '{}.zip'.format(launchpad_id)
+ os.remove(file_path)
+ file_handle = open(file_path, 'r')
+ file_handle.close()
+ except Exception as error:
+ app.logger.error("Error removing or closing downloaded file handle", error)
+ return response
+ return send_file('{}.zip'.format(launchpad_id), mimetype='zip',
+ attachment_filename='{}.zip'.format(launchpad_id), as_attachment=True, cache_timeout=0)
+
+
+@app.route('/file_exists/', methods=['GET', 'POST'])
+def file_exists():
+ file_name = request.args.get('file_name')
+ launchpad_id = request.args.get('launchpad_id')
+ if not is_allowed(file_name):
+ return '-1'
+ with g.connection.cursor() as cursor:
+ file_name_sql = "SELECT id FROM files WHERE launchpad_id=%s AND name=%s AND user_id=%s;"
+ cursor.execute(file_name_sql, (launchpad_id, secure_filename(file_name), g.user['id']))
+ if cursor.fetchone():
+ return '1'
+ else:
+ return '0'
+
+
+@app.route('/view_log/', methods=['GET', 'POST'])
+def view_log():
+ return send_file('collect.log', mimetype='text/plain')
+
+
+if __name__ == "__main__":
+ app.run(debug=True, host='0.0.0.0')
diff --git a/tools/collect_filesystem/app/requirements.txt b/tools/collect_filesystem/app/requirements.txt
new file mode 100644
index 00000000..888244dc
--- /dev/null
+++ b/tools/collect_filesystem/app/requirements.txt
@@ -0,0 +1,2 @@
+Flask==1.1.1
+mysql-connector
\ No newline at end of file
diff --git a/tools/collect_filesystem/app/static/main.js b/tools/collect_filesystem/app/static/main.js
new file mode 100644
index 00000000..48f09cc6
--- /dev/null
+++ b/tools/collect_filesystem/app/static/main.js
@@ -0,0 +1,415 @@
+function ConfirmDelete(elem) {
+ localStorage.setItem('deleteId', $(elem).attr('data-id'));
+ $('#deleteModal').modal();
+}
+
+function Delete() {
+ $.ajax({
+ url: '/delete_file',
+ data: {
+ id: localStorage.getItem('deleteId')
+ },
+ type: 'POST',
+ success: function(res) {
+ $('#deleteModal').modal('hide');
+ location.reload();
+ },
+ error: function(error) {
+ console.log(error);
+ }
+ });
+}
+
+// Get a reference to the progress bar, wrapper & status label
+var progress_wrapper = document.getElementById("progress_wrapper");
+
+// Get a reference to the 3 buttons
+var upload_btn = document.getElementById("upload_btn");
+var loading_btn = document.getElementById("loading_btn");
+var cancel_btn = document.getElementById("cancel_btn");
+
+// Get a reference to the alert wrapper
+var alert_wrapper = document.getElementById("alert_wrapper");
+
+// Get a reference to the file input element & input label
+var input = document.getElementById("file_input");
+var launchpad_input = document.getElementById("launchpad_input");
+var file_input_label = document.getElementById("file_input_label");
+
+var search_input_label = document.getElementById("search_input_label");
+var launchpad_info = document.getElementById("launchpad_info");
+
+var upload_count = 0;
+
+function search() {
+ search_input = search_input_label.value;
+ location.search = "?search="+search_input;
+}
+
+// Function to show alerts
+function show_alert(message, alert) {
+
+ alert_wrapper.innerHTML = alert_wrapper.innerHTML + `
+
+ ${message}
+
+
+ `
+
+}
+
+function revert() {
+ var tbody = $('table tbody');
+ tbody.html($('tr',tbody).get().reverse());
+ if (document.getElementById("table_order").classList.contains("fa-caret-square-o-down")) {
+ document.getElementById("table_order").classList.remove("fa-caret-square-o-down");
+ document.getElementById("table_order").classList.add("fa-caret-square-o-up");
+ } else {
+ document.getElementById("table_order").classList.remove("fa-caret-square-o-up");
+ document.getElementById("table_order").classList.add("fa-caret-square-o-down");
+ }
+}
+
+function upload() {
+
+ upload_count = 0
+
+ // Reject if the file input is empty & throw alert
+ if (!input.value) {
+
+ show_alert("No file selected", "warning")
+
+ return;
+
+ }
+
+ var request = new XMLHttpRequest();
+ var launchpad_id = launchpad_input.value;
+
+ request.open("get", "http://128.224.141.2:5000/check_launchpad/"+launchpad_id);
+ request.send();
+
+ // Clear any existing alerts
+ alert_wrapper.innerHTML = "";
+
+ request.addEventListener("load", function (e) {
+
+ if (request.status == 200) {
+
+ // Hide the upload button
+ upload_btn.classList.add("d-none");
+
+ // Show the loading button
+ loading_btn.classList.remove("d-none");
+
+ // Show the cancel button
+ cancel_btn.classList.remove("d-none");
+
+ // Show the progress bar
+ progress_wrapper.classList.remove("d-none");
+
+ // Show the progress bar
+ launchpad_info.classList.remove("d-none");
+
+ launchpad_info.innerHTML = 'Launchpad title: '+request.response;
+
+ // Disable the input during upload
+ input.disabled = true;
+ launchpad_input.disabled = true;
+
+ progress_wrapper.innerHTML = ""
+
+ for (var i = 0; i < input.files.length; i++) {
+ progress_wrapper.innerHTML = progress_wrapper.innerHTML + `
+
+
+
+
+
+
+
+
+
+
`
+ }
+
+ for (var i = 0; i < input.files.length; i++) {
+ upload_single_file(input.files[i], i);
+ }
+
+ }
+ else {
+
+ // Reset the input placeholder
+ file_input_label.innerText = "Select file or drop it here to upload";
+ launchpad_input.innerText = "";
+
+ show_alert(`${request.response}`, "danger");
+
+ }
+
+ });
+
+// reset();
+}
+
+// Function to upload single file
+function upload_single_file(file, i) {
+
+ var progress_wrapper_single = document.getElementById(`progress_wrapper_${i}`);
+ var progress = document.getElementById(`progress_${i}`);
+ var progress_status = document.getElementById(`progress_status_${i}`);
+
+ var cancel_btn_single = document.getElementById(`cancel_btn_${i}`);
+ var ignore_btn_single = document.getElementById(`ignore_btn_${i}`);
+ var overwrite_btn_single = document.getElementById(`overwrite_btn_${i}`);
+ var rename_btn_single = document.getElementById(`rename_btn_${i}`);
+
+ var url = "http://128.224.141.2:5000/upload/"
+
+ // Create a new FormData instance
+ var data = new FormData();
+
+ // Create a XMLHTTPRequest instance
+ var request = new XMLHttpRequest();
+ var request_file_check = new XMLHttpRequest();
+
+ // Set the response type
+ request.responseType = "json";
+
+ // Get a reference to the files
+// var file = input.files[0];
+ // Get a reference to the launchpad id
+ var launchpad_id = launchpad_input.value;
+
+// // Get a reference to the filename
+// var filename = file.name;
+
+ // Get a reference to the filesize & set a cookie
+// var filesize = file.size;
+// document.cookie = `filesize=${filesize}`;
+
+ // Append the file to the FormData instance
+// data.append("file", file);
+
+ request_file_check.open("get", '/file_exists/?launchpad_id='+launchpad_id+'&file_name='+file.name);
+ request_file_check.send();
+
+ request_file_check.addEventListener("load", function (e) {
+ if (request_file_check.responseText == '0'){
+ // Open and send the request
+ request.open("post", url+"?launchpad_id="+launchpad_id);
+ data = new FormData();
+ data.append("file", file);
+ request.send(data);
+ } else if (request_file_check.responseText == '1'){
+ progress_status.innerText = `File already exists: ${file.name}`;
+ progress_status.style.color = 'red';
+ cancel_btn_single.classList.add("d-none");
+ ignore_btn_single.classList.remove("d-none");
+ overwrite_btn_single.classList.remove("d-none");
+ rename_btn_single.classList.remove("d-none");
+ } else {
+ show_alert('Error: you did not supply a valid file in your request', "warning");
+ upload_count++;
+
+ if (upload_count == input.files.length){
+ reset();
+ }
+ }
+ });
+
+ ignore_btn_single.addEventListener("click", function () {
+
+ progress_status.style.color = 'black';
+
+ cancel_btn_single.classList.remove("d-none");
+ ignore_btn_single.classList.add("d-none");
+ overwrite_btn_single.classList.add("d-none");
+ rename_btn_single.classList.add("d-none");
+
+ show_alert(`Upload cancelled: ${file.name}`, "primary");
+
+ progress_wrapper_single.classList.add("d-none");
+
+ upload_count++;
+
+ if (upload_count == input.files.length){
+ reset();
+ }
+
+ })
+
+ overwrite_btn_single.addEventListener("click", function () {
+
+ progress_status.style.color = 'black';
+
+ cancel_btn_single.classList.remove("d-none");
+ ignore_btn_single.classList.add("d-none");
+ overwrite_btn_single.classList.add("d-none");
+ rename_btn_single.classList.add("d-none");
+
+ request.open("post", url+"?launchpad_id="+launchpad_id+'&conflict=0');
+ data = new FormData();
+ data.append("file", file);
+ request.send(data);
+
+ })
+
+ rename_btn_single.addEventListener("click", function () {
+
+ progress_status.style.color = 'black';
+
+ cancel_btn_single.classList.remove("d-none");
+ ignore_btn_single.classList.add("d-none");
+ overwrite_btn_single.classList.add("d-none");
+ rename_btn_single.classList.add("d-none");
+
+ request.open("post", url+"?launchpad_id="+launchpad_id+'&conflict=1');
+ data = new FormData();
+ data.append("file", file);
+ request.send(data);
+
+ })
+
+ // request progress handler
+ request.upload.addEventListener("progress", function (e) {
+
+ // Get the loaded amount and total filesize (bytes)
+ var loaded = e.loaded;
+ var total = e.total;
+
+ // Calculate percent uploaded
+ var percent_complete = (loaded / total) * 100;
+
+ // Update the progress text and progress bar
+ progress.setAttribute("style", `width: ${Math.floor(percent_complete)}%`);
+ progress_status.innerText = `${Math.floor(percent_complete)}% uploaded: ${file.name}`;
+
+ if (loaded == total) {
+ progress_status.innerText = `Saving file: ${file.name}`;
+ }
+
+ })
+
+ // request load handler (transfer complete)
+ request.addEventListener("load", function (e) {
+
+ if (request.status == 200) {
+
+ show_alert(`${request.response.message}`, "success");
+
+ }
+ else {
+
+ show_alert(`${request.response.message}`, "danger");
+
+ }
+
+ progress_wrapper_single.classList.add("d-none");
+
+ upload_count++;
+
+ if (upload_count == input.files.length){
+ reset();
+ }
+
+ });
+
+ // request error handler
+ request.addEventListener("error", function (e) {
+
+ show_alert(`Error uploading file: ${file.name}`, "warning");
+
+ progress_wrapper_single.classList.add("d-none");
+
+ upload_count++;
+
+ if (upload_count == input.files.length){
+ reset();
+ }
+
+ });
+
+ // request abort handler
+ request.addEventListener("abort", function (e) {
+
+ show_alert(`Upload cancelled: ${file.name}`, "primary");
+
+ progress_wrapper_single.classList.add("d-none");
+
+ upload_count++;
+
+ if (upload_count == input.files.length){
+ reset();
+ }
+
+ });
+
+ cancel_btn.addEventListener("click", function () {
+
+ request.abort();
+
+ })
+
+ cancel_btn_single.addEventListener("click", function () {
+
+ request.abort();
+
+ })
+
+}
+
+// Function to update the input placeholder
+function input_filename() {
+// file_input_label.innerText = typeof input.files;
+// var all_files = input.files.values().reduce(function (accumulator, file) {
+// return accumulator + file.name;
+// }, 0);
+
+ var all_files = input.files[0].name;
+
+ for (var i = 1; i < input.files.length; i++){
+ all_files = all_files + ', ' + input.files[i].name
+ }
+ file_input_label.innerText = all_files;
+
+// file_input_label.innerText = input.files[0].name;
+// file_input_label.innerText = input.files.toString();
+
+}
+
+// Function to reset the page
+function reset() {
+
+ // Clear the input
+ input.value = null;
+
+ // Hide the cancel button
+ cancel_btn.classList.add("d-none");
+
+ // Reset the input element
+ input.disabled = false;
+ launchpad_input.disabled = false;
+
+ // Show the upload button
+ upload_btn.classList.remove("d-none");
+
+ // Hide the loading button
+ loading_btn.classList.add("d-none");
+
+ // Hide the progress bar
+ progress_wrapper.classList.add("d-none");
+
+ // Reset the input placeholder
+ file_input_label.innerText = "Select file or drop it here to upload";
+ launchpad_input.innerText = "";
+
+ // Show the progress bar
+ launchpad_info.classList.add("d-none");
+
+ launchpad_info.innerHTML = "";
+
+}
\ No newline at end of file
diff --git a/tools/collect_filesystem/app/static/openid.png b/tools/collect_filesystem/app/static/openid.png
new file mode 100644
index 00000000..ce7954ab
Binary files /dev/null and b/tools/collect_filesystem/app/static/openid.png differ
diff --git a/tools/collect_filesystem/app/static/style.css b/tools/collect_filesystem/app/static/style.css
new file mode 100644
index 00000000..a52b96a6
--- /dev/null
+++ b/tools/collect_filesystem/app/static/style.css
@@ -0,0 +1,46 @@
+body {
+ font-family: 'Georgia', serif;
+ font-size: 16px;
+ margin: 30px;
+ padding: 0;
+}
+
+a {
+ color: #335E79;
+}
+
+p.message {
+ color: #335E79;
+ padding: 10px;
+ background: #CADEEB;
+}
+
+p.error {
+ color: #783232;
+ padding: 10px;
+ background: #EBCACA;
+}
+
+input {
+ font-family: 'Georgia', serif;
+ font-size: 16px;
+ border: 1px solid black;
+ color: #335E79;
+ padding: 2px;
+}
+
+input[type="submit"] {
+ background: #CADEEB;
+ color: #335E79;
+ border-color: #335E79;
+}
+
+#basic-addon3 {
+ background: url(openid.png) 4px no-repeat;
+ background-color: #e9ecef;
+ padding-left: 24px;
+}
+
+h1, h2 {
+ font-weight: normal;
+}
diff --git a/tools/collect_filesystem/app/templates/413.html b/tools/collect_filesystem/app/templates/413.html
new file mode 100644
index 00000000..9f3a2dc2
--- /dev/null
+++ b/tools/collect_filesystem/app/templates/413.html
@@ -0,0 +1,7 @@
+{% extends "layout.html" %}
+{% block title %}Page Not Found{% endblock %}
+{% block body %}
+
Page Not Found
+
What you were looking for is just not there.
+
go somewhere nice
+{% endblock %}
\ No newline at end of file
diff --git a/tools/collect_filesystem/app/templates/_confirm.html b/tools/collect_filesystem/app/templates/_confirm.html
new file mode 100644
index 00000000..b8caecb1
--- /dev/null
+++ b/tools/collect_filesystem/app/templates/_confirm.html
@@ -0,0 +1,7 @@
+
+
{{ desc }}
+
+
\ No newline at end of file
diff --git a/tools/collect_filesystem/app/templates/bak/upload.html b/tools/collect_filesystem/app/templates/bak/upload.html
new file mode 100644
index 00000000..e513f4b3
--- /dev/null
+++ b/tools/collect_filesystem/app/templates/bak/upload.html
@@ -0,0 +1,51 @@
+{% extends "layout.html" %}
+{% block title %}Sign in{% endblock %}
+{% block body %}
+
Upload
+
+
+
+
+
+
+
+
Are you sure you want to overwrite this file?
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tools/collect_filesystem/app/templates/create_profile.html b/tools/collect_filesystem/app/templates/create_profile.html
new file mode 100644
index 00000000..e024a627
--- /dev/null
+++ b/tools/collect_filesystem/app/templates/create_profile.html
@@ -0,0 +1,28 @@
+{% extends "layout.html" %}
+{% block title %}Create Profile{% endblock %}
+{% block body %}
+
Create Profile
+
+ Hey! This is the first time you signed in on this website. In
+ order to proceed we need some extra information from you:
+
+
+ If you don't want to proceed, you can sign out again.
+{% endblock %}
diff --git a/tools/collect_filesystem/app/templates/edit_file.html b/tools/collect_filesystem/app/templates/edit_file.html
new file mode 100644
index 00000000..f942632a
--- /dev/null
+++ b/tools/collect_filesystem/app/templates/edit_file.html
@@ -0,0 +1,27 @@
+{% extends "layout.html" %}
+{% block title %}Edit Profile{% endblock %}
+{% block body %}
+
Edit File
+
+{% endblock %}
diff --git a/tools/collect_filesystem/app/templates/edit_profile.html b/tools/collect_filesystem/app/templates/edit_profile.html
new file mode 100644
index 00000000..ffaf1e17
--- /dev/null
+++ b/tools/collect_filesystem/app/templates/edit_profile.html
@@ -0,0 +1,22 @@
+{% extends "layout.html" %}
+{% block title %}Edit Profile{% endblock %}
+{% block body %}
+
+ This is just an example page so that something is here.
+{% endblock %}
diff --git a/tools/collect_filesystem/app/templates/launchpad.html b/tools/collect_filesystem/app/templates/launchpad.html
new file mode 100644
index 00000000..86e3baa3
--- /dev/null
+++ b/tools/collect_filesystem/app/templates/launchpad.html
@@ -0,0 +1,40 @@
+{% extends "layout.html" %}
+{% block body %}
+