updated server

This commit is contained in:
2025-06-20 16:33:21 +03:00
parent 2e94a334de
commit c57bb7fe6d
10 changed files with 87 additions and 91 deletions

View File

@@ -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.

53
app.py
View File

@@ -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.

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -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:
file_response = requests.get(file_url, timeout=10)
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: else:
print(f'Failed to download {file_name}: {response.status_code}') 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}')