Skip to content

Commit c744dca

Browse files
committed
Merge branch 'main' of github.com:emagedoc/CodeClash
2 parents d2444cf + d2939a8 commit c744dca

8 files changed

Lines changed: 868 additions & 1263 deletions

File tree

codeclash/utils/update_tracker.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@
2020
tracker[arena][setting][k] = [0, 0]
2121

2222
for arena_log in arena_logs:
23-
arena = arena_log.stem.split(".", 2)[1]
24-
k = arena_log.stem.split(".", 2)[-1]
25-
pvp = k.split(".", 3)[-1]
26-
setting = k[: -len(pvp) - 1]
23+
name = arena_log.stem
24+
if name.endswith("-uuid"):
25+
# Strip uuid
26+
name = name.rpartition(".")[0]
27+
arena = name.split(".", 2)[1]
28+
k = name.split(".", 2)[-1]
29+
pvp = k.split(".", 3)[-1] # (the model pair)
30+
setting = k[: -len(pvp) - 1] # (number of rounds etc.)
2731
if arena == "RoboCode":
2832
with open(arena_log / "metadata.json") as f:
2933
metadata = json.load(f)

codeclash/viewer/app.py

Lines changed: 21 additions & 236 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ def _load_game_metadata(folder_info: dict[str, Any]) -> dict[str, Any]:
395395
folder_info["models"] = metadata.models
396396
folder_info["game_name"] = metadata.game_name
397397
folder_info["created_timestamp"] = metadata.get_path("created_timestamp")
398+
folder_info["aws_command"] = metadata.get_path("aws.AWS_USER_PROVIDED_COMMAND")
398399
return folder_info
399400

400401

@@ -409,20 +410,18 @@ def _find_all_game_folders_impl(base_dir: Path) -> list[dict[str, Any]]:
409410

410411
# Create folder info for each game folder
411412
game_folder_infos = []
412-
game_folder_paths = set()
413413

414414
for metadata_file in metadata_files:
415415
game_dir = metadata_file.parent
416416
relative_path = str(game_dir.relative_to(base_dir))
417-
game_folder_paths.add(relative_path)
418-
depth = relative_path.count("/")
417+
418+
# Extract parent folder path (folder containing the game folder)
419+
parent_folder = str(game_dir.parent.relative_to(base_dir)) if game_dir.parent != base_dir else ""
419420

420421
folder_info = {
421422
"name": relative_path,
422423
"full_path": str(game_dir),
423-
"is_game": True,
424-
"depth": depth,
425-
"parent": str(game_dir.parent.relative_to(base_dir)) if game_dir.parent != base_dir else None,
424+
"parent_folder": parent_folder,
426425
}
427426
game_folder_infos.append(folder_info)
428427

@@ -444,38 +443,7 @@ def _find_all_game_folders_impl(base_dir: Path) -> list[dict[str, Any]]:
444443
folder_info["created_timestamp"] = None
445444
logger.warning(f"Failed to load metadata for {folder_info['name']}: {e}")
446445

447-
# Create intermediate folder entries for all parent directories
448-
intermediate_folders = set()
449-
for game_path in game_folder_paths:
450-
# Extract all parent paths
451-
parts = game_path.split("/")
452-
for i in range(1, len(parts)):
453-
parent_path = "/".join(parts[:i])
454-
if parent_path not in game_folder_paths: # Only add if not a game folder itself
455-
intermediate_folders.add(parent_path)
456-
457-
# Add intermediate folders to the result
458-
all_folders = game_folder_infos.copy()
459-
for intermediate_path in intermediate_folders:
460-
depth = intermediate_path.count("/")
461-
parent_parts = intermediate_path.split("/")
462-
parent_path = "/".join(parent_parts[:-1]) if len(parent_parts) > 1 else None
463-
464-
all_folders.append(
465-
{
466-
"name": intermediate_path,
467-
"full_path": str(base_dir / intermediate_path),
468-
"is_game": False,
469-
"depth": depth,
470-
"parent": parent_path,
471-
"round_info": None,
472-
"models": [],
473-
"game_name": "",
474-
"created_timestamp": None,
475-
}
476-
)
477-
478-
return sorted(all_folders, key=lambda x: x["name"])
446+
return sorted(game_folder_infos, key=lambda x: x["name"])
479447

480448

481449
@print_timing
@@ -1012,14 +980,14 @@ def get_parent_folder(path):
1012980

1013981

1014982
def format_timestamp(timestamp):
1015-
"""Format Unix timestamp as YYYY-MM-DD HH:MM"""
983+
"""Format Unix timestamp as MM/DD HH:MM"""
1016984
if timestamp is None:
1017985
return ""
1018986
from datetime import datetime
1019987

1020988
try:
1021989
dt = datetime.fromtimestamp(timestamp)
1022-
return dt.strftime("%Y-%m-%d %H:%M")
990+
return dt.strftime("%m/%d %H:%M")
1023991
except (ValueError, OSError):
1024992
return ""
1025993

@@ -1039,8 +1007,8 @@ def get_navigation_info(selected_folder: str) -> dict[str, str | None]:
10391007
# Get all game folders
10401008
game_folders = find_all_game_folders(LOG_BASE_DIR)
10411009

1042-
# Filter to only actual game folders and sort them
1043-
game_names = [folder["name"] for folder in game_folders if folder["is_game"]]
1010+
# Extract game folder names
1011+
game_names = [folder["name"] for folder in game_folders]
10441012
game_names.sort()
10451013

10461014
# Find current game index
@@ -1148,11 +1116,20 @@ def game_view(folder_path):
11481116
@app.route("/picker")
11491117
@print_timing
11501118
def game_picker():
1151-
"""Game picker page with recursive folder support"""
1119+
"""Game picker page with flat folder structure"""
11521120
logs_dir = LOG_BASE_DIR
11531121
game_folders = find_all_game_folders(logs_dir)
11541122

1155-
return render_template("picker.html", game_folders=game_folders, base_dir=str(logs_dir), is_static=STATIC_MODE)
1123+
# Extract unique parent folders for the folder dropdown
1124+
unique_folders = sorted({folder["parent_folder"] for folder in game_folders})
1125+
1126+
return render_template(
1127+
"picker.html",
1128+
game_folders=game_folders,
1129+
unique_folders=unique_folders,
1130+
base_dir=str(logs_dir),
1131+
is_static=STATIC_MODE,
1132+
)
11561133

11571134

11581135
@app.route("/delete-experiment", methods=["POST"])
@@ -1191,198 +1168,6 @@ def delete_experiment():
11911168
return jsonify({"success": False, "error": str(e)})
11921169

11931170

1194-
@app.route("/rename-folders", methods=["POST"])
1195-
def rename_folders():
1196-
"""Add suffix to selected folders"""
1197-
try:
1198-
data = request.get_json()
1199-
action = data.get("action")
1200-
paths = data.get("paths", [])
1201-
suffix = data.get("suffix", "")
1202-
1203-
if not paths:
1204-
return jsonify({"success": False, "error": "No paths provided"})
1205-
1206-
if action != "add-suffix":
1207-
return jsonify({"success": False, "error": "Invalid action"})
1208-
1209-
if not suffix:
1210-
return jsonify({"success": False, "error": "No suffix provided"})
1211-
1212-
successful_renames = []
1213-
failed_renames = []
1214-
1215-
for relative_path in paths:
1216-
try:
1217-
# Get the full path
1218-
old_path = LOG_BASE_DIR / relative_path
1219-
1220-
if not old_path.exists():
1221-
failed_renames.append(f"{relative_path}: does not exist")
1222-
continue
1223-
1224-
# Create new path with suffix
1225-
parent_dir = old_path.parent
1226-
old_name = old_path.name
1227-
new_name = f"{old_name}.{suffix}"
1228-
new_path = parent_dir / new_name
1229-
1230-
# Check if target already exists
1231-
if new_path.exists():
1232-
failed_renames.append(f"{relative_path}: target already exists")
1233-
continue
1234-
1235-
# Perform the rename
1236-
old_path.rename(new_path)
1237-
successful_renames.append(f"{relative_path}{old_name}.{suffix}")
1238-
1239-
except Exception as e:
1240-
failed_renames.append(f"{relative_path}: {str(e)}")
1241-
1242-
if failed_renames:
1243-
error_msg = "Some renames failed:\n" + "\n".join(failed_renames)
1244-
if successful_renames:
1245-
error_msg += "\n\nSuccessful renames:\n" + "\n".join(successful_renames)
1246-
return jsonify({"success": False, "error": error_msg})
1247-
1248-
return jsonify(
1249-
{
1250-
"success": True,
1251-
"message": f"Successfully renamed {len(successful_renames)} folder(s)",
1252-
"renamed": successful_renames,
1253-
}
1254-
)
1255-
1256-
except Exception as e:
1257-
return jsonify({"success": False, "error": str(e)})
1258-
1259-
1260-
@app.route("/move-to-subfolder", methods=["POST"])
1261-
def move_to_subfolder():
1262-
"""Move selected folders to a new subfolder"""
1263-
try:
1264-
data = request.get_json()
1265-
paths = data.get("paths", [])
1266-
subfolder_name = data.get("subfolder", "")
1267-
1268-
if not paths:
1269-
return jsonify({"success": False, "error": "No paths provided"})
1270-
1271-
if not subfolder_name:
1272-
return jsonify({"success": False, "error": "No subfolder name provided"})
1273-
1274-
# Validate subfolder name (no path separators, etc.)
1275-
if "/" in subfolder_name or "\\" in subfolder_name or ".." in subfolder_name:
1276-
return jsonify({"success": False, "error": "Invalid subfolder name"})
1277-
1278-
successful_moves = []
1279-
failed_moves = []
1280-
1281-
for relative_path in paths:
1282-
try:
1283-
# Get the full path
1284-
old_path = LOG_BASE_DIR / relative_path
1285-
1286-
if not old_path.exists():
1287-
failed_moves.append(f"{relative_path}: does not exist")
1288-
continue
1289-
1290-
# Determine where to create the subfolder
1291-
parent_dir = old_path.parent
1292-
subfolder_path = parent_dir / subfolder_name
1293-
1294-
# Create subfolder if it doesn't exist
1295-
subfolder_path.mkdir(exist_ok=True)
1296-
1297-
# Create new path inside subfolder
1298-
folder_name = old_path.name
1299-
new_path = subfolder_path / folder_name
1300-
1301-
# Check if target already exists
1302-
if new_path.exists():
1303-
failed_moves.append(f"{relative_path}: target already exists in subfolder")
1304-
continue
1305-
1306-
# Perform the move
1307-
old_path.rename(new_path)
1308-
1309-
# Calculate the new relative path for display
1310-
new_relative_path = str(new_path.relative_to(LOG_BASE_DIR))
1311-
successful_moves.append(f"{relative_path}{new_relative_path}")
1312-
1313-
except Exception as e:
1314-
failed_moves.append(f"{relative_path}: {str(e)}")
1315-
1316-
if failed_moves:
1317-
error_msg = "Some moves failed:\n" + "\n".join(failed_moves)
1318-
if successful_moves:
1319-
error_msg += "\n\nSuccessful moves:\n" + "\n".join(successful_moves)
1320-
return jsonify({"success": False, "error": error_msg})
1321-
1322-
return jsonify(
1323-
{
1324-
"success": True,
1325-
"message": f"Successfully moved {len(successful_moves)} folder(s) to subfolder '{subfolder_name}'",
1326-
"moved": successful_moves,
1327-
}
1328-
)
1329-
1330-
except Exception as e:
1331-
return jsonify({"success": False, "error": str(e)})
1332-
1333-
1334-
@app.route("/move-folder", methods=["POST"])
1335-
def move_folder():
1336-
"""Move/rename a single folder to a new path"""
1337-
try:
1338-
data = request.get_json()
1339-
old_path = data.get("old_path", "")
1340-
new_path = data.get("new_path", "")
1341-
1342-
if not old_path:
1343-
return jsonify({"success": False, "error": "No old path provided"})
1344-
1345-
if not new_path:
1346-
return jsonify({"success": False, "error": "No new path provided"})
1347-
1348-
# Convert to Path objects relative to LOG_BASE_DIR
1349-
old_full_path = LOG_BASE_DIR / old_path
1350-
new_full_path = LOG_BASE_DIR / new_path
1351-
1352-
# Validate old path exists
1353-
if not old_full_path.exists():
1354-
return jsonify({"success": False, "error": "Source folder does not exist"})
1355-
1356-
# Validate new path doesn't already exist
1357-
if new_full_path.exists():
1358-
return jsonify({"success": False, "error": "Target path already exists"})
1359-
1360-
# Security check: ensure both paths are within our expected logs directory
1361-
try:
1362-
old_full_path.relative_to(LOG_BASE_DIR)
1363-
new_full_path.relative_to(LOG_BASE_DIR)
1364-
except ValueError:
1365-
return jsonify({"success": False, "error": "Invalid path - must be within logs directory"})
1366-
1367-
# Create intermediate directories if necessary
1368-
new_full_path.parent.mkdir(parents=True, exist_ok=True)
1369-
1370-
# Perform the move
1371-
old_full_path.rename(new_full_path)
1372-
1373-
return jsonify(
1374-
{
1375-
"success": True,
1376-
"message": f"Successfully moved folder from '{old_path}' to '{new_path}'",
1377-
"old_path": old_path,
1378-
"new_path": new_path,
1379-
}
1380-
)
1381-
1382-
except Exception as e:
1383-
return jsonify({"success": False, "error": str(e)})
1384-
1385-
13861171
@timeout(120)
13871172
def _analyze_line_counts_impl(folder_path: Path):
13881173
"""Internal implementation of line counts analysis with timeout"""

0 commit comments

Comments
 (0)