From 50c8843ef8db32e836e8381960df1d6642f701d2 Mon Sep 17 00:00:00 2001 From: Shane Israel Date: Fri, 17 Apr 2026 21:20:47 -0600 Subject: [PATCH 1/5] fix default media privacy not affecting images. make images automatically appear after upload --- app/client/src/views/ImageFeed.js | 33 ++++++++++++++++++++++++++++++- app/client/src/views/Settings.js | 1 + app/server/fireshare/constants.py | 3 +++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app/client/src/views/ImageFeed.js b/app/client/src/views/ImageFeed.js index 51af3ff1..42e4118b 100644 --- a/app/client/src/views/ImageFeed.js +++ b/app/client/src/views/ImageFeed.js @@ -27,10 +27,11 @@ import SnackbarAlert from '../components/alert/SnackbarAlert' import selectSortTheme from '../common/reactSelectSortTheme' import { SORT_OPTIONS } from '../common/constants' -const ImageFeed = ({ authenticated, searchText, cardSize, selectedImageFolder, onImageFoldersLoaded }) => { +const ImageFeed = ({ authenticated, searchText, cardSize, selectedImageFolder, onImageFoldersLoaded, uploadTick }) => { const [images, setImages] = React.useState([]) const [filteredImages, setFilteredImages] = React.useState([]) const [loading, setLoading] = React.useState(true) + const imageCountRef = React.useRef(0) const [search, setSearch] = React.useState(searchText) const [alert, setAlert] = React.useState({ open: false }) const [modalImage, setModalImage] = React.useState(null) @@ -108,6 +109,36 @@ const ImageFeed = ({ authenticated, searchText, cardSize, selectedImageFolder, o // eslint-disable-next-line }, [authenticated]) + React.useEffect(() => { + if (uploadTick === 0) return + imageCountRef.current = images.length + let attempts = 0 + const interval = setInterval(() => { + attempts++ + const fetchFn = authenticated ? ImageService.getImages : ImageService.getPublicImages + fetchFn().then((res) => { + const fetched = res.data.images + if (fetched.length > imageCountRef.current || attempts >= 8) { + clearInterval(interval) + setImages(fetched) + setFilteredImages(fetched) + const tfolders = [] + fetched.forEach((img) => { + const split = img.path + .split('/') + .slice(0, -1) + .filter((f) => f !== '') + if (split.length > 0 && !tfolders.includes(split[0])) tfolders.push(split[0]) + }) + tfolders.sort((a, b) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1)).unshift('All Images') + if (onImageFoldersLoaded) onImageFoldersLoaded(tfolders) + } + }) + }, 2000) + return () => clearInterval(interval) + // eslint-disable-next-line + }, [uploadTick]) + const folder = selectedImageFolder || { value: 'All Images', label: 'All Images' } const displayImages = React.useMemo(() => { diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index feff2102..bce93669 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -565,6 +565,7 @@ const Settings = () => { app_config: { ...prev.app_config, video_defaults: { private: !prev.app_config.video_defaults.private }, + image_defaults: { private: !prev.app_config.video_defaults.private }, }, })) }} diff --git a/app/server/fireshare/constants.py b/app/server/fireshare/constants.py index 24edf385..91b56050 100644 --- a/app/server/fireshare/constants.py +++ b/app/server/fireshare/constants.py @@ -3,6 +3,9 @@ "video_defaults": { "private": True }, + "image_defaults": { + "private": True + }, "allow_public_upload": True, "allow_public_folder_selection": True, "allow_public_game_tag": True, From 57b90268413ff0cc0d93f39872a19a111dc61eea Mon Sep 17 00:00:00 2001 From: Shane Israel Date: Fri, 17 Apr 2026 21:37:48 -0600 Subject: [PATCH 2/5] automatically add image_defaults config to existing systems that do not have it --- app/server/fireshare/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/server/fireshare/__init__.py b/app/server/fireshare/__init__.py index b254319a..14339054 100644 --- a/app/server/fireshare/__init__.py +++ b/app/server/fireshare/__init__.py @@ -95,6 +95,10 @@ def combine(dict1, dict2): except: logger.error(f"Invalid config.json file at {str(path)}, exiting...") sys.exit() + # If image_defaults is missing, inherit from video_defaults so privacy stays consistent + if 'image_defaults' not in current.get('app_config', {}): + video_private = current.get('app_config', {}).get('video_defaults', {}).get('private', True) + current.setdefault('app_config', {})['image_defaults'] = {'private': video_private} updated = combine(DEFAULT_CONFIG, current) path.write_text(json.dumps(updated, indent=2)) configfile.close() From d0b4288f9efcf84f98e1ebbc54d9c14603d16dd4 Mon Sep 17 00:00:00 2001 From: Shane Israel Date: Sat, 18 Apr 2026 13:19:49 -0600 Subject: [PATCH 3/5] add improved config.json validation and recovery logic. If invalid, backup the old and create a new one --- app/server/fireshare/__init__.py | 11 +++++++---- app/server/fireshare/constants.py | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/server/fireshare/__init__.py b/app/server/fireshare/__init__.py index 14339054..cebcfa03 100644 --- a/app/server/fireshare/__init__.py +++ b/app/server/fireshare/__init__.py @@ -1,4 +1,4 @@ -import os, sys, re +import os, sys, re, copy import os.path try: import ldap @@ -93,13 +93,16 @@ def combine(dict1, dict2): try: current = json.load(configfile) except: - logger.error(f"Invalid config.json file at {str(path)}, exiting...") - sys.exit() + backup = path.with_suffix('.json.bak') + logger.warning(f"Invalid config.json at {str(path)}, backing up to {str(backup)} and resetting to defaults") + path.rename(backup) + path.write_text(json.dumps(DEFAULT_CONFIG, indent=2)) + current = copy.deepcopy(DEFAULT_CONFIG) # If image_defaults is missing, inherit from video_defaults so privacy stays consistent if 'image_defaults' not in current.get('app_config', {}): video_private = current.get('app_config', {}).get('video_defaults', {}).get('private', True) current.setdefault('app_config', {})['image_defaults'] = {'private': video_private} - updated = combine(DEFAULT_CONFIG, current) + updated = combine(copy.deepcopy(DEFAULT_CONFIG), current) path.write_text(json.dumps(updated, indent=2)) configfile.close() diff --git a/app/server/fireshare/constants.py b/app/server/fireshare/constants.py index 91b56050..022ee615 100644 --- a/app/server/fireshare/constants.py +++ b/app/server/fireshare/constants.py @@ -20,7 +20,8 @@ "show_my_videos": True, "show_public_upload": False, "show_public_videos": True, - "show_images": True + "show_images": True, + "autoplay": False }, "integrations": { "discord_webhook_url": "", From cae5cdf054782a9d24da8b46ef45236f0594bb28 Mon Sep 17 00:00:00 2001 From: Shane Israel Date: Sat, 18 Apr 2026 14:05:35 -0600 Subject: [PATCH 4/5] improve the reliability of creating the config.json file through creation of a temp file first and then renaming --- .../src/components/cards/ImageUploadCard.js | 28 +++++++++-------- app/server/fireshare/__init__.py | 30 ++++++++++++++----- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/app/client/src/components/cards/ImageUploadCard.js b/app/client/src/components/cards/ImageUploadCard.js index 0472fd09..746ddbe6 100644 --- a/app/client/src/components/cards/ImageUploadCard.js +++ b/app/client/src/components/cards/ImageUploadCard.js @@ -168,6 +168,7 @@ const ImageUploadCard = React.forwardRef(function ImageUploadCard( const handleUpload = async () => { setUploading(true) const game_id = selectedGame?.id || null + let successCount = 0 for (let i = 0; i < pendingFiles.length; i++) { setUploadIndex(i) @@ -181,22 +182,25 @@ const ImageUploadCard = React.forwardRef(function ImageUploadCard( try { const uploadFn = authenticated ? ImageService.upload : ImageService.publicUpload await uploadFn(formData, (progress) => setUploadProgress(progress)) + successCount++ } catch (err) { - handleAlert({ - type: 'error', - message: `Failed to upload ${file.name}`, - open: true, - }) + const serverMessage = err?.response?.data + const message = serverMessage + ? `Failed to upload ${file.name}: ${serverMessage}` + : `Failed to upload ${file.name}` + handleAlert({ type: 'error', message, open: true }) } } - handleAlert({ - type: 'success', - message: `${pendingFiles.length} image${pendingFiles.length > 1 ? 's' : ''} uploaded — they'll appear shortly.`, - autohideDuration: 3500, - open: true, - }) - if (onUploadComplete) onUploadComplete() + if (successCount > 0) { + handleAlert({ + type: 'success', + message: `${successCount} image${successCount > 1 ? 's' : ''} uploaded — they'll appear shortly.`, + autohideDuration: 3500, + open: true, + }) + if (onUploadComplete) onUploadComplete() + } cleanup() setDialogOpen(false) } diff --git a/app/server/fireshare/__init__.py b/app/server/fireshare/__init__.py index cebcfa03..7084ff3c 100644 --- a/app/server/fireshare/__init__.py +++ b/app/server/fireshare/__init__.py @@ -1,4 +1,4 @@ -import os, sys, re, copy +import os, sys, re, copy, tempfile import os.path try: import ldap @@ -85,26 +85,32 @@ def combine(dict1, dict2): dict1[key] = dict2[key] return dict1 + def atomic_write(target, content): + dir = target.parent + with tempfile.NamedTemporaryFile('w', dir=dir, delete=False, suffix='.tmp') as tmp: + tmp.write(content) + tmp_path = tmp.name + os.replace(tmp_path, target) + from .constants import DEFAULT_CONFIG if not path.exists(): - path.write_text(json.dumps(DEFAULT_CONFIG, indent=2)) + atomic_write(path, json.dumps(DEFAULT_CONFIG, indent=2)) - with open(path, 'r+') as configfile: + with open(path, 'r') as configfile: try: current = json.load(configfile) except: backup = path.with_suffix('.json.bak') logger.warning(f"Invalid config.json at {str(path)}, backing up to {str(backup)} and resetting to defaults") path.rename(backup) - path.write_text(json.dumps(DEFAULT_CONFIG, indent=2)) + atomic_write(path, json.dumps(DEFAULT_CONFIG, indent=2)) current = copy.deepcopy(DEFAULT_CONFIG) # If image_defaults is missing, inherit from video_defaults so privacy stays consistent if 'image_defaults' not in current.get('app_config', {}): video_private = current.get('app_config', {}).get('video_defaults', {}).get('private', True) current.setdefault('app_config', {})['image_defaults'] = {'private': video_private} updated = combine(copy.deepcopy(DEFAULT_CONFIG), current) - path.write_text(json.dumps(updated, indent=2)) - configfile.close() + atomic_write(path, json.dumps(updated, indent=2)) def create_app(init_schedule=False): app = Flask(__name__, static_url_path='', static_folder='build', template_folder='build') @@ -192,12 +198,22 @@ def create_app(init_schedule=False): app.config['WARNINGS'].append(steamgridWarning) logger.warning(steamgridWarning) + for env_var, mount_path, message in [ + ('DATA_DIRECTORY', '/data', 'Data will not persist. Mount a directory to /data to persist data.'), + ('VIDEO_DIRECTORY', '/videos', 'Data will not persist. Mount a directory to /videos to persist data.'), + ('PROCESSED_DIRECTORY', '/processed', 'Data will not persist. Mount a directory to /processed to persist data.'), + ('IMAGE_DIRECTORY', '/images', 'Data will not persist. Mount a directory to /images to persist data.'), + ]: + if app.config.get(env_var) and not os.path.ismount(app.config[env_var]): + logger.warning(f"No volume is mounted to {mount_path}. {message}") + paths = { 'data': Path(app.config['DATA_DIRECTORY']), 'video': Path(app.config['VIDEO_DIRECTORY']), 'processed': Path(app.config['PROCESSED_DIRECTORY']), - 'image': Path(app.config['IMAGE_DIRECTORY']), } + if app.config['IMAGE_DIRECTORY']: + paths['image'] = Path(app.config['IMAGE_DIRECTORY']) app.config['PATHS'] = paths for k, path in paths.items(): if not path.is_dir(): From 6437f6e31c9efeb8a175571855b742ee81b535f7 Mon Sep 17 00:00:00 2001 From: Shane Israel Date: Sat, 18 Apr 2026 14:33:01 -0600 Subject: [PATCH 5/5] bump version to 1.6.3 --- app/client/package-lock.json | 2 +- app/client/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/client/package-lock.json b/app/client/package-lock.json index 38bdf97b..f8a2bfab 100644 --- a/app/client/package-lock.json +++ b/app/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "fireshare", - "version": "1.6.2", + "version": "1.6.3", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/app/client/package.json b/app/client/package.json index d7e9ede7..cd59d65c 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -1,6 +1,6 @@ { "name": "fireshare", - "version": "1.6.2", + "version": "1.6.3", "private": true, "dependencies": { "@emotion/react": "^11.9.0",