updated server
This commit is contained in:
@@ -1,67 +0,0 @@
|
|||||||
# Stage 1: Build stage
|
|
||||||
FROM python:3.11-slim AS build
|
|
||||||
|
|
||||||
# Set the working directory in the container
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install build tools and libraries
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
libffi-dev \
|
|
||||||
libssl-dev \
|
|
||||||
python3-dev \
|
|
||||||
cargo \
|
|
||||||
g++ \
|
|
||||||
curl \
|
|
||||||
libjpeg-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install Rust
|
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
|
||||||
|
|
||||||
# Upgrade pip to the latest version
|
|
||||||
RUN pip install --upgrade pip
|
|
||||||
|
|
||||||
# Copy the current directory contents into the container at /app
|
|
||||||
COPY . /app
|
|
||||||
|
|
||||||
# Install any needed packages specified in requirements.txt
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
|
||||||
|
|
||||||
# Stage 2: Runtime stage
|
|
||||||
FROM python:3.11-slim
|
|
||||||
|
|
||||||
# Set the working directory in the container
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install runtime dependencies
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
libffi-dev \
|
|
||||||
libssl-dev \
|
|
||||||
g++ \
|
|
||||||
curl \
|
|
||||||
libjpeg-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install Rust
|
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
|
||||||
|
|
||||||
# Copy only the necessary files from the build stage
|
|
||||||
COPY --from=build /app /app
|
|
||||||
COPY --from=build /root/.cargo /root/.cargo
|
|
||||||
|
|
||||||
# Install Gunicorn and other Python dependencies
|
|
||||||
COPY requirements.txt .
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
|
||||||
RUN pip install gunicorn
|
|
||||||
RUN apt-get update && apt-get install -y libreoffice poppler-utils ffmpeg
|
|
||||||
|
|
||||||
# Make port 5000 available to the world outside this container
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
# Run the clear_db script, then the initialization script, and then Gunicorn
|
|
||||||
CMD ["sh", "-c", "python clear_db.py && python init_db.py && gunicorn -w 4 -b 0.0.0.0:5000 app:app"]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
53
app.py
53
app.py
@@ -62,19 +62,28 @@ def admin_required(f):
|
|||||||
def add_image_to_playlist(file, filename, duration, target_type, target_id):
|
def add_image_to_playlist(file, filename, duration, target_type, target_id):
|
||||||
"""
|
"""
|
||||||
Save the image file and add it to the playlist database.
|
Save the image file and add it to the playlist database.
|
||||||
|
Increment the playlist version for the player or group.
|
||||||
"""
|
"""
|
||||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||||
# Only save if file does not already exist (prevents double-saving)
|
# Only save if file does not already exist (prevents double-saving)
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
file.save(file_path)
|
file.save(file_path)
|
||||||
|
|
||||||
if target_type == 'group':
|
if target_type == 'group':
|
||||||
group = Group.query.get_or_404(target_id)
|
group = Group.query.get_or_404(target_id)
|
||||||
for player in group.players:
|
for player in group.players:
|
||||||
new_content = Content(file_name=filename, duration=duration, player_id=player.id)
|
new_content = Content(file_name=filename, duration=duration, player_id=player.id)
|
||||||
db.session.add(new_content)
|
db.session.add(new_content)
|
||||||
|
# Increment playlist version for the group
|
||||||
|
group.playlist_version += 1
|
||||||
elif target_type == 'player':
|
elif target_type == 'player':
|
||||||
|
player = Player.query.get_or_404(target_id)
|
||||||
new_content = Content(file_name=filename, duration=duration, player_id=target_id)
|
new_content = Content(file_name=filename, duration=duration, player_id=target_id)
|
||||||
db.session.add(new_content)
|
db.session.add(new_content)
|
||||||
|
# Increment playlist version for the player
|
||||||
|
player.playlist_version += 1
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
def convert_ppt_to_images(input_file, output_folder):
|
def convert_ppt_to_images(input_file, output_folder):
|
||||||
"""
|
"""
|
||||||
@@ -597,20 +606,36 @@ def clean_unused_files():
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/playlists', methods=['GET'])
|
@app.route('/api/playlists', methods=['GET'])
|
||||||
def get_playlist():
|
def get_playlists():
|
||||||
hostname = request.args.get('hostname')
|
hostname = request.args.get('hostname')
|
||||||
quickconnect_code = request.args.get('quickconnect_code')
|
quickconnect_code = request.args.get('quickconnect_code')
|
||||||
|
|
||||||
|
# Validate the parameters
|
||||||
if not hostname or not quickconnect_code:
|
if not hostname or not quickconnect_code:
|
||||||
return jsonify({'error': 'Hostname and quick connect code are required'}), 400
|
return jsonify({'error': 'Hostname and quick connect code are required'}), 400
|
||||||
|
|
||||||
|
# Find the player by hostname and verify the quickconnect code
|
||||||
player = Player.query.filter_by(hostname=hostname).first()
|
player = Player.query.filter_by(hostname=hostname).first()
|
||||||
if not player or not player.verify_quickconnect_code(quickconnect_code):
|
if not player or not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
||||||
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
|
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
|
||||||
|
|
||||||
|
# Query the Content table for media files associated with the player
|
||||||
content = Content.query.filter_by(player_id=player.id).all()
|
content = Content.query.filter_by(player_id=player.id).all()
|
||||||
|
playlist = [
|
||||||
|
{
|
||||||
|
'file_name': media.file_name,
|
||||||
|
'url': f"http://{request.host}/media/{media.file_name}",
|
||||||
|
'duration': media.duration
|
||||||
|
}
|
||||||
|
for media in content
|
||||||
|
]
|
||||||
|
|
||||||
playlist = [{'file_name': item.file_name, 'duration': item.duration, 'url': url_for('media', filename=item.file_name, _external=True)} for item in content]
|
# Return the playlist, version, and hashed quickconnect code
|
||||||
return jsonify({'playlist': playlist})
|
return jsonify({
|
||||||
|
'playlist': playlist,
|
||||||
|
'playlist_version': player.playlist_version,
|
||||||
|
'hashed_quickconnect': player.quickconnect_password
|
||||||
|
})
|
||||||
|
|
||||||
@app.route('/media/<path:filename>')
|
@app.route('/media/<path:filename>')
|
||||||
def media(filename):
|
def media(filename):
|
||||||
@@ -720,6 +745,26 @@ def delete_group_media(group_id, content_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('manage_group', group_id=group_id))
|
return redirect(url_for('manage_group', group_id=group_id))
|
||||||
|
|
||||||
|
@app.route('/api/playlist_version', methods=['GET'])
|
||||||
|
def get_playlist_version():
|
||||||
|
hostname = request.args.get('hostname')
|
||||||
|
quickconnect_code = request.args.get('quickconnect_code')
|
||||||
|
|
||||||
|
# Validate the parameters
|
||||||
|
if not hostname or not quickconnect_code:
|
||||||
|
return jsonify({'error': 'Hostname and quick connect code are required'}), 400
|
||||||
|
|
||||||
|
# Find the player by hostname and verify the quickconnect code
|
||||||
|
player = Player.query.filter_by(hostname=hostname).first()
|
||||||
|
if not player or not bcrypt.check_password_hash(player.quickconnect_password, quickconnect_code):
|
||||||
|
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
|
||||||
|
|
||||||
|
# Return the playlist version and hashed quickconnect code
|
||||||
|
return jsonify({
|
||||||
|
'playlist_version': player.playlist_version,
|
||||||
|
'hashed_quickconnect': player.quickconnect_password
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
Binary file not shown.
10
models.py
10
models.py
@@ -12,10 +12,11 @@ class Content(db.Model):
|
|||||||
|
|
||||||
class Player(db.Model):
|
class Player(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
username = db.Column(db.String(100), nullable=False, unique=True)
|
||||||
hostname = db.Column(db.String(120), unique=True, nullable=False)
|
hostname = db.Column(db.String(100), nullable=False)
|
||||||
password = db.Column(db.String(120), nullable=False)
|
password = db.Column(db.String(200), nullable=False)
|
||||||
quickconnect_password = db.Column(db.String(120), nullable=False)
|
quickconnect_password = db.Column(db.String(200), nullable=True) # Add this field
|
||||||
|
playlist_version = db.Column(db.Integer, default=0) # Playlist version counter
|
||||||
|
|
||||||
def verify_quickconnect_code(self, code):
|
def verify_quickconnect_code(self, code):
|
||||||
return bcrypt.check_password_hash(self.quickconnect_password, code)
|
return bcrypt.check_password_hash(self.quickconnect_password, code)
|
||||||
@@ -52,6 +53,7 @@ class Group(db.Model):
|
|||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(100), nullable=False, unique=True)
|
name = db.Column(db.String(100), nullable=False, unique=True)
|
||||||
players = db.relationship('Player', secondary='group_player', backref='groups')
|
players = db.relationship('Player', secondary='group_player', backref='groups')
|
||||||
|
playlist_version = db.Column(db.Integer, default=0) # Playlist version counter
|
||||||
|
|
||||||
# Association table for many-to-many relationship between Group and Player
|
# Association table for many-to-many relationship between Group and Player
|
||||||
group_player = db.Table('group_player',
|
group_player = db.Table('group_player',
|
||||||
|
|||||||
BIN
static/uploads/delete.png
Normal file
BIN
static/uploads/delete.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
BIN
static/uploads/edit_pencil.png
Normal file
BIN
static/uploads/edit_pencil.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
48
test_api.py
48
test_api.py
@@ -3,10 +3,10 @@ import os
|
|||||||
|
|
||||||
# Replace with the actual server IP address or domain name, hostname, and quick connect code
|
# Replace with the actual server IP address or domain name, hostname, and quick connect code
|
||||||
server_ip = 'http://localhost:5000'
|
server_ip = 'http://localhost:5000'
|
||||||
hostname = 'tv1'
|
hostname = 'rpi-tv11'
|
||||||
quickconnect_code = '4321'
|
quickconnect_code = '8887779'
|
||||||
|
|
||||||
# Construct the URL with the hostname and quick connect code as query parameters
|
# Construct the URL for the playlist API
|
||||||
url = f'{server_ip}/api/playlists'
|
url = f'{server_ip}/api/playlists'
|
||||||
params = {
|
params = {
|
||||||
'hostname': hostname,
|
'hostname': hostname,
|
||||||
@@ -24,21 +24,37 @@ print(f'Response Content: {response.text}')
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
try:
|
try:
|
||||||
# Parse the JSON response
|
# Parse the JSON response
|
||||||
playlist = response.json().get('playlist', [])
|
response_data = response.json()
|
||||||
print('Playlist:', playlist)
|
playlist = response_data.get('playlist', [])
|
||||||
|
playlist_version = response_data.get('playlist_version', None)
|
||||||
|
|
||||||
|
print(f'Playlist Version: {playlist_version}')
|
||||||
|
print(f'Playlist: {playlist}')
|
||||||
|
|
||||||
|
# Define the local folder for saving files
|
||||||
|
local_folder = './static/resurse'
|
||||||
|
if not os.path.exists(local_folder):
|
||||||
|
os.makedirs(local_folder)
|
||||||
|
|
||||||
# Download each file in the playlist
|
# Download each file in the playlist
|
||||||
for item in playlist:
|
for media in playlist:
|
||||||
file_url = item['url']
|
file_name = media.get('file_name', '')
|
||||||
file_name = item['file_name']
|
file_url = media.get('url', '')
|
||||||
response = requests.get(file_url)
|
duration = media.get('duration', 10) # Default duration if not provided
|
||||||
if response.status_code == 200:
|
local_file_path = os.path.join(local_folder, file_name)
|
||||||
with open(file_name, 'wb') as f:
|
|
||||||
f.write(response.content)
|
print(f'Downloading {file_name} from {file_url}...')
|
||||||
print(f'Downloaded {file_name}')
|
try:
|
||||||
else:
|
file_response = requests.get(file_url, timeout=10)
|
||||||
print(f'Failed to download {file_name}: {response.status_code}')
|
if file_response.status_code == 200:
|
||||||
|
with open(local_file_path, 'wb') as file:
|
||||||
|
file.write(file_response.content)
|
||||||
|
print(f'Successfully downloaded {file_name} to {local_file_path}')
|
||||||
|
else:
|
||||||
|
print(f'Failed to download {file_name}. Status Code: {file_response.status_code}')
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f'Error downloading {file_name}: {e}')
|
||||||
except requests.exceptions.JSONDecodeError as e:
|
except requests.exceptions.JSONDecodeError as e:
|
||||||
print(f'Failed to parse JSON response: {e}')
|
print(f'Failed to parse JSON response: {e}')
|
||||||
else:
|
else:
|
||||||
print('Failed to retrieve playlist:', response.text)
|
print(f'Failed to retrieve playlist. Status Code: {response.status_code}')
|
||||||
Reference in New Issue
Block a user