fix: deduplicate soundfont scan + source label + delete only uploads
- _scan_soundfonts: dict-keyed by base_id to prevent duplicates
- Add 'source' field ('system' | 'upload') to each soundfont entry
- Delete endpoint: only allow deleting upload soundfonts (403 for system)
- Frontend: show (system)/(upload) tag, hide Delete for system fonts
This commit is contained in:
@@ -103,14 +103,16 @@ async def upload_soundfont(
|
|||||||
async def delete_soundfont(sf_id: str, current_user: dict = Depends(get_current_user)):
|
async def delete_soundfont(sf_id: str, current_user: dict = Depends(get_current_user)):
|
||||||
base_id = sf_id.replace("sf_", "")
|
base_id = sf_id.replace("sf_", "")
|
||||||
deleted = False
|
deleted = False
|
||||||
for d in [UPLOAD_SF_DIR, os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "static", "soundfonts")]:
|
for d in [UPLOAD_SF_DIR, os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "static", "soundfonts"), SYSTEM_SF_DIR]:
|
||||||
if not os.path.isdir(d):
|
if not os.path.isdir(d):
|
||||||
continue
|
continue
|
||||||
for f in os.listdir(d):
|
for f in os.listdir(d):
|
||||||
if os.path.splitext(f)[0] == base_id:
|
if os.path.splitext(f)[0] == base_id:
|
||||||
|
# Skip system dir — only allow deleting uploads
|
||||||
|
if d == SYSTEM_SF_DIR:
|
||||||
|
raise HTTPException(status_code=403, detail="System soundfonts cannot be deleted via this endpoint")
|
||||||
path = os.path.join(d, f)
|
path = os.path.join(d, f)
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
# Remove associated .meta file
|
|
||||||
meta_path = os.path.join(d, os.path.splitext(f)[0] + ".meta")
|
meta_path = os.path.join(d, os.path.splitext(f)[0] + ".meta")
|
||||||
if os.path.isfile(meta_path):
|
if os.path.isfile(meta_path):
|
||||||
os.remove(meta_path)
|
os.remove(meta_path)
|
||||||
|
|||||||
+14
-8
@@ -163,12 +163,11 @@ class PluginManager:
|
|||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
def _scan_soundfonts(self) -> list:
|
def _scan_soundfonts(self) -> list:
|
||||||
sfonts = []
|
sf_map = {}
|
||||||
dirs = [self.sf_dir]
|
dirs = [("system", self.sf_dir)]
|
||||||
if self.upload_sf_dir and self.upload_sf_dir != self.sf_dir:
|
if self.upload_sf_dir and self.upload_sf_dir != self.sf_dir:
|
||||||
dirs.append(self.upload_sf_dir)
|
dirs.append(("upload", self.upload_sf_dir))
|
||||||
|
|
||||||
# Load metadata cache for upload soundfonts
|
|
||||||
meta_cache = {}
|
meta_cache = {}
|
||||||
if self.upload_sf_dir and os.path.isdir(self.upload_sf_dir):
|
if self.upload_sf_dir and os.path.isdir(self.upload_sf_dir):
|
||||||
for f in os.listdir(self.upload_sf_dir):
|
for f in os.listdir(self.upload_sf_dir):
|
||||||
@@ -180,21 +179,28 @@ class PluginManager:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
for d in dirs:
|
for source, d in dirs:
|
||||||
if not os.path.isdir(d):
|
if not os.path.isdir(d):
|
||||||
continue
|
continue
|
||||||
for f in os.listdir(d):
|
for f in os.listdir(d):
|
||||||
if f.endswith(".sf2") or f.endswith(".sf3"):
|
if f.endswith(".sf2") or f.endswith(".sf3"):
|
||||||
base_id = os.path.splitext(f)[0]
|
base_id = os.path.splitext(f)[0]
|
||||||
|
if base_id in sf_map:
|
||||||
|
continue
|
||||||
meta = meta_cache.get(base_id, None)
|
meta = meta_cache.get(base_id, None)
|
||||||
if meta:
|
if meta:
|
||||||
display_name = meta.get("original_name", f)
|
display_name = meta.get("original_name", f)
|
||||||
else:
|
else:
|
||||||
# Generate a friendly name from UUID: truncate to first 8 chars
|
|
||||||
short_id = base_id[:8] if len(base_id) > 8 else base_id
|
short_id = base_id[:8] if len(base_id) > 8 else base_id
|
||||||
display_name = f"SoundFont_{short_id}"
|
display_name = f"SoundFont_{short_id}"
|
||||||
sfonts.append({"id": base_id, "name": display_name, "file": f, "display": os.path.splitext(display_name)[0][:40]})
|
sf_map[base_id] = {
|
||||||
return sfonts
|
"id": base_id,
|
||||||
|
"name": display_name,
|
||||||
|
"file": f,
|
||||||
|
"display": os.path.splitext(display_name)[0][:40],
|
||||||
|
"source": source
|
||||||
|
}
|
||||||
|
return list(sf_map.values())
|
||||||
|
|
||||||
def load_vst(self, plugin_name: str, preset_data: dict = None):
|
def load_vst(self, plugin_name: str, preset_data: dict = None):
|
||||||
if not HAS_PEDALBOARD:
|
if not HAS_PEDALBOARD:
|
||||||
|
|||||||
@@ -3459,12 +3459,17 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData }) => {
|
|||||||
React.createElement('div', { className: 'text-xs font-semibold text-slate-200' }, sf.display || sf.name || sf.id),
|
React.createElement('div', { className: 'text-xs font-semibold text-slate-200' }, sf.display || sf.name || sf.id),
|
||||||
React.createElement('div', { className: 'text-[10px] text-zinc-500' }, sf.file || sf.name)
|
React.createElement('div', { className: 'text-[10px] text-zinc-500' }, sf.file || sf.name)
|
||||||
)
|
)
|
||||||
|
), React.createElement('div', {
|
||||||
|
className: 'text-[10px] text-zinc-500'
|
||||||
|
}, sf.file || sf.name, ' ', React.createElement('span', {
|
||||||
|
className: sf.source === 'system' ? 'text-blue-400' : 'text-amber-500'
|
||||||
|
}, sf.source === 'system' ? '(system)' : '(upload)'))
|
||||||
),
|
),
|
||||||
React.createElement('div', { className: 'flex items-center gap-2' },
|
React.createElement('div', { className: 'flex items-center gap-2' },
|
||||||
React.createElement('button', {
|
sf.source === 'upload' ? React.createElement('button', {
|
||||||
onClick: () => setSfToDelete(sf),
|
onClick: () => setSfToDelete(sf),
|
||||||
className: 'text-[10px] text-zinc-500 hover:text-red-400 opacity-0 group-hover:opacity-100 transition px-2 py-1'
|
className: 'text-[10px] text-zinc-500 hover:text-red-400 opacity-0 group-hover:opacity-100 transition px-2 py-1'
|
||||||
}, 'Delete')
|
}, 'Delete') : React.createElement('span', { className: 'text-[10px] text-zinc-700' }, 'System')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user