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):
"""
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)
# Only save if file does not already exist (prevents double-saving)
if not os.path.exists(file_path):
file.save(file_path)
if target_type == 'group':
group = Group.query.get_or_404(target_id)
for player in group.players:
new_content = Content(file_name=filename, duration=duration, player_id=player.id)
db.session.add(new_content)
# Increment playlist version for the group
group.playlist_version += 1
elif target_type == 'player':
player = Player.query.get_or_404(target_id)
new_content = Content(file_name=filename, duration=duration, player_id=target_id)
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):
"""
@@ -597,20 +606,36 @@ def clean_unused_files():
@app.route('/api/playlists', methods=['GET'])
def get_playlist():
def get_playlists():
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 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
# Query the Content table for media files associated with the player
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 jsonify({'playlist': playlist})
# Return the playlist, version, and hashed quickconnect code
return jsonify({
'playlist': playlist,
'playlist_version': player.playlist_version,
'hashed_quickconnect': player.quickconnect_password
})
@app.route('/media/<path:filename>')
def media(filename):
@@ -720,6 +745,26 @@ def delete_group_media(group_id, content_id):
db.session.commit()
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__':
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):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
hostname = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
quickconnect_password = db.Column(db.String(120), nullable=False)
username = db.Column(db.String(100), nullable=False, unique=True)
hostname = db.Column(db.String(100), nullable=False)
password = db.Column(db.String(200), 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):
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)
name = db.Column(db.String(100), nullable=False, unique=True)
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
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
server_ip = 'http://localhost:5000'
hostname = 'tv1'
quickconnect_code = '4321'
hostname = 'rpi-tv11'
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'
params = {
'hostname': hostname,
@@ -24,21 +24,37 @@ print(f'Response Content: {response.text}')
if response.status_code == 200:
try:
# Parse the JSON response
playlist = response.json().get('playlist', [])
print('Playlist:', playlist)
response_data = response.json()
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
for item in playlist:
file_url = item['url']
file_name = item['file_name']
response = requests.get(file_url)
if response.status_code == 200:
with open(file_name, 'wb') as f:
f.write(response.content)
print(f'Downloaded {file_name}')
else:
print(f'Failed to download {file_name}: {response.status_code}')
for media in playlist:
file_name = media.get('file_name', '')
file_url = media.get('url', '')
duration = media.get('duration', 10) # Default duration if not provided
local_file_path = os.path.join(local_folder, file_name)
print(f'Downloading {file_name} from {file_url}...')
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:
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:
print(f'Failed to parse JSON response: {e}')
else:
print('Failed to retrieve playlist:', response.text)
print(f'Failed to retrieve playlist. Status Code: {response.status_code}')