Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fireshare",
"version": "1.6.2",
"version": "1.6.3",
"private": true,
"dependencies": {
"@emotion/react": "^11.9.0",
Expand Down
28 changes: 16 additions & 12 deletions app/client/src/components/cards/ImageUploadCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
33 changes: 32 additions & 1 deletion app/client/src/views/ImageFeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(() => {
Expand Down
1 change: 1 addition & 0 deletions app/client/src/views/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
},
}))
}}
Expand Down
41 changes: 32 additions & 9 deletions app/server/fireshare/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os, sys, re
import os, sys, re, copy, tempfile
import os.path
try:
import ldap
Expand Down Expand Up @@ -85,19 +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:
logger.error(f"Invalid config.json file at {str(path)}, exiting...")
sys.exit()
updated = combine(DEFAULT_CONFIG, current)
path.write_text(json.dumps(updated, indent=2))
configfile.close()
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)
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)
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')
Expand Down Expand Up @@ -185,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():
Expand Down
6 changes: 5 additions & 1 deletion app/server/fireshare/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -17,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": "",
Expand Down
Loading