|
import re
|
|
import subprocess
|
|
|
|
from flask import Flask, request, jsonify, send_file
|
|
from flask_socketio import SocketIO, emit
|
|
from flask_cors import CORS
|
|
from logger import Logger
|
|
from uuid import uuid4
|
|
import os
|
|
|
|
app = Flask(__name__)
|
|
|
|
logger = Logger('log.txt')
|
|
|
|
app.config['SECRET_KEY'] = 'secret!'
|
|
CORS(app, resources={r"/*": {"origins": "*"}})
|
|
socketio = SocketIO(app, cors_allowed_origins="*")
|
|
|
|
|
|
|
|
|
|
clients = []
|
|
|
|
SIMULATE = False
|
|
TEST = False
|
|
|
|
"""
|
|
client_downloads is a 'dict'
|
|
with format as shown below
|
|
{
|
|
'clientId':
|
|
{
|
|
'downloadId':
|
|
[
|
|
[
|
|
['fileName','id'],
|
|
'done?'
|
|
]
|
|
]
|
|
}
|
|
|
|
downloadId is a unique id for each download (ref: downloadId in download.py)
|
|
downloadId is generated by the server as opposed to clientId which is generated by the client
|
|
|
|
in earlier versions of the server, downloadId was generated by the client
|
|
- these versions used static site, as opposed to the current version which uses 'react'
|
|
|
|
downloadId is further a list with a list of 2 elements
|
|
- first element is the list of fileName and id
|
|
- second element is a integer value
|
|
0 -> download has been completed
|
|
1 -> download is in progress
|
|
-1 -> download has been cancelled
|
|
|
|
"""
|
|
client_downloads = {}
|
|
|
|
|
|
def shell(command, filter='.*', clientId='None', stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
universal_newlines=True):
|
|
import os, time
|
|
|
|
|
|
os.environ['PYTHONUNBUFFERED'] = '1'
|
|
|
|
command = to_list(command)
|
|
count = 0
|
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True,
|
|
shell=True)
|
|
output_list = []
|
|
|
|
while True:
|
|
output = process.stdout.readline()
|
|
if output == '' and process.poll() is not None:
|
|
break
|
|
if output and filter is not None and re.search(filter, output):
|
|
print(output.strip())
|
|
|
|
if output not in output_list:
|
|
socketio.emit(f'progress-{clientId}', {
|
|
'message': output.strip(),
|
|
'count': count
|
|
})
|
|
output_list.append(output.strip())
|
|
count += 1
|
|
|
|
|
|
return_code = process.poll()
|
|
|
|
return return_code
|
|
|
|
|
|
def to_list(variable):
|
|
if isinstance(variable, list):
|
|
return variable
|
|
elif variable is None:
|
|
return []
|
|
else:
|
|
|
|
return variable.split()
|
|
|
|
|
|
def check_for_existing_running_downloads(clientId, downloadId=None):
|
|
|
|
|
|
|
|
|
|
if clientId not in clients:
|
|
return False
|
|
|
|
|
|
if clientId not in client_downloads:
|
|
return False
|
|
|
|
|
|
if downloadId:
|
|
downloads_of_client = client_downloads[clientId]
|
|
|
|
|
|
if downloadId not in downloads_of_client:
|
|
return False
|
|
|
|
downloads_by_downloadId = downloads_of_client[downloadId]
|
|
for download in downloads_by_downloadId:
|
|
if download[1] == 1:
|
|
return downloadId
|
|
|
|
|
|
for downloadId in client_downloads[clientId]:
|
|
downloads_by_downloadId = client_downloads[clientId][downloadId]
|
|
for download in downloads_by_downloadId:
|
|
if download[1] == 1:
|
|
return downloadId
|
|
|
|
|
|
return False
|
|
|
|
|
|
def send_existing_download_status(clientId):
|
|
emit(f'exists-running-downloads-{clientId}', {
|
|
'message': 'Client has existing downloads',
|
|
'names': [download[0][0] for download in
|
|
client_downloads[clientId][check_for_existing_running_downloads(clientId)]],
|
|
'ids': [download[0][1] for download in
|
|
client_downloads[clientId][check_for_existing_running_downloads(clientId)]],
|
|
})
|
|
|
|
|
|
@socketio.on('connect')
|
|
def connect(data):
|
|
pass
|
|
|
|
|
|
@socketio.on('connect-with-client-Id')
|
|
def connect_with_client_id(data):
|
|
clientId = data['clientId']
|
|
|
|
if not clientId:
|
|
logger.logError(f'Empty CLient ID by {request.sid}')
|
|
|
|
if clientId not in clients:
|
|
logger.logInfo(f'Client {clientId} connected')
|
|
clients.append(clientId)
|
|
|
|
if not clientId in client_downloads:
|
|
client_downloads[clientId] = {
|
|
|
|
}
|
|
|
|
if check_for_existing_running_downloads(clientId):
|
|
logger.logInfo(f'Client {clientId} has existing downloads')
|
|
send_existing_download_status(clientId)
|
|
return
|
|
|
|
|
|
else:
|
|
logger.logInfo(f'Client {clientId} has no existing downloads')
|
|
|
|
emit(
|
|
f'successful-{clientId}',
|
|
{
|
|
'connected': True
|
|
}
|
|
)
|
|
|
|
|
|
@socketio.on('test-pwdl')
|
|
def test_pwdl(data):
|
|
names = data['names']
|
|
ids = data['IDs']
|
|
clientId = data['clientId']
|
|
|
|
existing_download_id = check_for_existing_running_downloads(clientId)
|
|
|
|
if existing_download_id:
|
|
download_id = existing_download_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
send_existing_download_status(clientId)
|
|
return
|
|
else:
|
|
download_id = str(uuid4())
|
|
client_downloads[clientId] = {
|
|
download_id: []
|
|
}
|
|
|
|
|
|
if TEST:
|
|
download_id = '13cac2f0-e46f-465b-8a2c-71a043b05bd0'
|
|
|
|
logger.logInfo(f'Client {clientId} requested download')
|
|
logger.logInfo(f'Client {clientId} requested download with assigned downloadId {download_id}')
|
|
|
|
|
|
|
|
try:
|
|
os.makedirs(f'./webdl/{clientId}/{download_id}', exist_ok=True)
|
|
except Exception as e:
|
|
logger.logError(f'Error creating directory {e} for client {clientId} with downloadId {download_id} ')
|
|
print(e)
|
|
return
|
|
|
|
|
|
|
|
for name, id in zip(names, ids):
|
|
|
|
if TEST: break
|
|
|
|
|
|
client_downloads[clientId][download_id].append([[name, id], 1])
|
|
|
|
|
|
|
|
command = ['python',
|
|
'pwdlv3/pwdl.py',
|
|
'--id', id,
|
|
'--name', name,
|
|
'--dir', f'./webdl/{clientId}/{download_id}',
|
|
'--verbose']
|
|
|
|
if SIMULATE: command.append('--simulate')
|
|
|
|
|
|
if SIMULATE:
|
|
with open(f'./webdl/{clientId}/{download_id}/{name}.txt', 'w') as f:
|
|
f.write(f'This is a simulated file for {name}')
|
|
|
|
|
|
socketio.emit(f'started-download-{clientId}', {
|
|
"name": name,
|
|
"id": id,
|
|
"downloadId": download_id
|
|
})
|
|
|
|
|
|
|
|
|
|
return_code_after_download = shell(command,
|
|
filter='.*',
|
|
clientId=clientId,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True)
|
|
|
|
print(f"Return code after download: {return_code_after_download}")
|
|
if return_code_after_download == 0:
|
|
|
|
client_downloads[clientId][download_id][-1][1] = 0
|
|
socketio.emit(f'partial-complete-download-{clientId}', {
|
|
"name": name,
|
|
"id": id,
|
|
"downloadId": download_id
|
|
})
|
|
else:
|
|
|
|
client_downloads[clientId][download_id][-1][1] = -1
|
|
socketio.emit(f'partial-cancel-download-{clientId}', {
|
|
"name": name,
|
|
"id": id,
|
|
"download_id": download_id
|
|
})
|
|
|
|
|
|
done = []
|
|
cancelled = []
|
|
|
|
if not TEST:
|
|
for download in client_downloads[clientId][download_id]:
|
|
if download[1] == 0:
|
|
done.append(download[0][0] + (".txt" if SIMULATE else ".mp4") )
|
|
elif download[1] == -1:
|
|
cancelled.append(download[0][0])
|
|
|
|
logger.logInfo(f'Client {clientId} has completed {len(done) + 1} downloads and cancelled {len(cancelled) + 1} downloads')
|
|
|
|
|
|
|
|
if TEST:
|
|
done = ["1.mp4", "2.mp4"]
|
|
|
|
print(f'Download ID: {download_id}')
|
|
|
|
socketio.emit(f'download-complete-{clientId}', {
|
|
'message': 'pwdl executed',
|
|
'downloadId': download_id,
|
|
'done': done,
|
|
'cancelled': cancelled
|
|
})
|
|
|
|
|
|
@app.route('/get')
|
|
def get():
|
|
|
|
downloadId = request.args.get('downloadId')
|
|
clientId = request.args.get('clientId')
|
|
fileName = request.args.get('fileName')
|
|
|
|
print(f'DownloadId {downloadId}')
|
|
|
|
file_path = f'./webdl/{clientId}/{downloadId}/{fileName}'
|
|
|
|
response = send_file(file_path, as_attachment=True)
|
|
response.headers['Content-Length'] = os.path.getsize(file_path)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
@app.route('/')
|
|
def hello_world():
|
|
return 'Hello World!'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
socketio.run(app, debug=True, port=5001)
|
|
|