codewithRiz commited on
Commit
4f1c854
Β·
1 Parent(s): 84b35ff

camera edit fixed

Browse files
Files changed (1) hide show
  1. api/camera.py +121 -34
api/camera.py CHANGED
@@ -1,20 +1,34 @@
1
- from fastapi import APIRouter, HTTPException, Query, Body
2
  from pydantic import BaseModel, Field, validator
3
  from typing import List, Optional
4
- from .utils import save_cameras, load_cameras, get_user_file, user_exists
5
- from .config import UPLOAD_DIR
 
 
 
 
 
 
 
 
 
 
 
 
6
  import os
7
- import shutil
8
 
9
  print(" CAMERA API LOADED ")
10
 
11
  router = APIRouter(prefix="/camera", tags=["Camera"])
12
 
 
13
  # ================= MODELS =================
 
14
  class CameraData(BaseModel):
15
  user_id: str = Field(..., min_length=1)
16
  camera_name: str = Field(..., min_length=1)
17
  camera_loc: Optional[List[float]] = None
 
18
  @validator("camera_loc")
19
  def validate_loc(cls, loc):
20
  if loc is None:
@@ -33,9 +47,12 @@ class EditCameraData(BaseModel):
33
  user_id: str
34
  old_camera_name: str
35
  new_camera_name: str
36
- new_camera_loc: List[float]
 
37
  @validator("new_camera_loc")
38
  def validate_loc(cls, loc):
 
 
39
  if len(loc) != 2:
40
  raise ValueError("new_camera_loc must be [lat, lon]")
41
  lat, lon = loc
@@ -47,6 +64,7 @@ class EditCameraData(BaseModel):
47
 
48
 
49
  # ================= ROUTES =================
 
50
  @router.get("/")
51
  def home():
52
  return {
@@ -59,6 +77,7 @@ def home():
59
  ]
60
  }
61
 
 
62
  # ---------- GET CAMERAS ----------
63
  @router.get("/get_cameras")
64
  def get_cameras(user_id: str = Query(...)):
@@ -81,58 +100,126 @@ def add_camera(data: CameraData):
81
  save_cameras(data.user_id, cameras)
82
  return {"success": True, "camera": data.camera_name}
83
 
 
 
84
  @router.put("/edit_camera")
85
  def edit_camera(data: EditCameraData):
86
  if not user_exists(data.user_id):
87
  raise HTTPException(status_code=404, detail="User not found")
 
88
  cameras = load_cameras(data.user_id)
89
- # Check if new camera name already exists (case-insensitive)
90
- if any(cam["camera_name"].lower() == data.new_camera_name.lower() for cam in cameras):
91
- raise HTTPException(status_code=400, detail=f"Camera name '{data.new_camera_name}' already exists")
 
 
 
 
 
 
 
92
  camera_found = False
93
  for cam in cameras:
94
  if cam["camera_name"].lower() == data.old_camera_name.lower():
95
  old_name = cam["camera_name"]
 
 
96
  cam["camera_name"] = data.new_camera_name
97
- cam["camera_loc"] = data.new_camera_loc
 
 
98
  camera_found = True
99
- # Rename camera folder
100
- old_folder = os.path.join(UPLOAD_DIR, data.user_id, old_name)
101
- new_folder = os.path.join(UPLOAD_DIR, data.user_id, data.new_camera_name)
102
- if os.path.exists(old_folder) and os.path.isdir(old_folder):
103
- try:
104
- os.rename(old_folder, new_folder)
105
- except Exception as e:
106
- raise HTTPException(status_code=500, detail=f"Failed to rename camera folder: {str(e)}")
107
- # Rename detection JSON
108
- for f in os.listdir(new_folder):
109
- if f.endswith("_detections.json") and f.lower().startswith(old_name.lower()):
110
- old_json = os.path.join(new_folder, f)
111
- new_json_name = f.replace(old_name, data.new_camera_name)
112
- new_json = os.path.join(new_folder, new_json_name)
113
- try:
114
- os.rename(old_json, new_json)
115
- except Exception as e:
116
- raise HTTPException(status_code=500, detail=f"Failed to rename detection JSON: {str(e)}")
117
- break # Only one detection JSON per camera
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  save_cameras(data.user_id, cameras)
119
  return {"success": True, "updated": cam}
 
120
  if not camera_found:
121
  raise HTTPException(status_code=404, detail="Camera not found")
122
 
 
 
123
  @router.delete("/delete_camera")
124
  def delete_camera(user_id: str = Query(...), camera_name: str = Query(...)):
125
  if not user_exists(user_id):
126
  raise HTTPException(status_code=404, detail="User not found")
 
127
  cameras = load_cameras(user_id)
128
  new_list = [c for c in cameras if c["camera_name"].lower() != camera_name.lower()]
129
  if len(new_list) == len(cameras):
130
  raise HTTPException(status_code=404, detail="Camera not found")
131
- save_cameras(user_id, new_list)
132
- camera_folder = os.path.join(UPLOAD_DIR, user_id, camera_name)
133
- if os.path.exists(camera_folder) and os.path.isdir(camera_folder):
 
 
 
 
134
  try:
135
- shutil.rmtree(camera_folder)
 
 
 
 
136
  except Exception as e:
137
- raise HTTPException(status_code=500, detail=f"Failed to delete camera folder: {str(e)}")
138
- return {"success": True, "deleted": camera_name}
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Query
2
  from pydantic import BaseModel, Field, validator
3
  from typing import List, Optional
4
+
5
+ from .utils import (
6
+ save_cameras,
7
+ load_cameras,
8
+ user_exists,
9
+ _bucket_key,
10
+ _list_prefix,
11
+ _read_bucket_json,
12
+ _write_bucket_json,
13
+ BUCKET_ID,
14
+ )
15
+
16
+ from huggingface_hub import batch_bucket_files, download_bucket_files
17
+ import tempfile
18
  import os
 
19
 
20
  print(" CAMERA API LOADED ")
21
 
22
  router = APIRouter(prefix="/camera", tags=["Camera"])
23
 
24
+
25
  # ================= MODELS =================
26
+
27
  class CameraData(BaseModel):
28
  user_id: str = Field(..., min_length=1)
29
  camera_name: str = Field(..., min_length=1)
30
  camera_loc: Optional[List[float]] = None
31
+
32
  @validator("camera_loc")
33
  def validate_loc(cls, loc):
34
  if loc is None:
 
47
  user_id: str
48
  old_camera_name: str
49
  new_camera_name: str
50
+ new_camera_loc: Optional[List[float]] = None # ← optional, won't error if missing
51
+
52
  @validator("new_camera_loc")
53
  def validate_loc(cls, loc):
54
+ if loc is None:
55
+ return loc
56
  if len(loc) != 2:
57
  raise ValueError("new_camera_loc must be [lat, lon]")
58
  lat, lon = loc
 
64
 
65
 
66
  # ================= ROUTES =================
67
+
68
  @router.get("/")
69
  def home():
70
  return {
 
77
  ]
78
  }
79
 
80
+
81
  # ---------- GET CAMERAS ----------
82
  @router.get("/get_cameras")
83
  def get_cameras(user_id: str = Query(...)):
 
100
  save_cameras(data.user_id, cameras)
101
  return {"success": True, "camera": data.camera_name}
102
 
103
+
104
+ # ---------- EDIT CAMERA ----------
105
  @router.put("/edit_camera")
106
  def edit_camera(data: EditCameraData):
107
  if not user_exists(data.user_id):
108
  raise HTTPException(status_code=404, detail="User not found")
109
+
110
  cameras = load_cameras(data.user_id)
111
+
112
+ # Only block duplicate name if name is actually changing
113
+ name_changing = data.new_camera_name.lower() != data.old_camera_name.lower()
114
+ if name_changing:
115
+ if any(cam["camera_name"].lower() == data.new_camera_name.lower() for cam in cameras):
116
+ raise HTTPException(
117
+ status_code=400,
118
+ detail=f"Camera name '{data.new_camera_name}' already exists"
119
+ )
120
+
121
  camera_found = False
122
  for cam in cameras:
123
  if cam["camera_name"].lower() == data.old_camera_name.lower():
124
  old_name = cam["camera_name"]
125
+
126
+ # Apply updates
127
  cam["camera_name"] = data.new_camera_name
128
+ if data.new_camera_loc is not None: # only update if provided
129
+ cam["camera_loc"] = data.new_camera_loc
130
+
131
  camera_found = True
132
+
133
+ # ── Rename bucket files only if name changed ──────────
134
+ if name_changing:
135
+ old_prefix = _bucket_key(data.user_id, old_name)
136
+ new_prefix = _bucket_key(data.user_id, data.new_camera_name)
137
+
138
+ old_files = _list_prefix(old_prefix)
139
+
140
+ for item in old_files:
141
+ old_key = item.path
142
+ new_key = old_key.replace(old_prefix, new_prefix, 1)
143
+
144
+ # Rename detection JSON filename inside the key
145
+ if f"{old_name}_detections.json" in new_key:
146
+ new_key = new_key.replace(
147
+ f"{old_name}_detections.json",
148
+ f"{data.new_camera_name}_detections.json"
149
+ )
150
+
151
+ try:
152
+ if old_key.endswith(".json"):
153
+ # JSON: read and re-write
154
+ content = _read_bucket_json(old_key)
155
+ if content is not None:
156
+ _write_bucket_json(new_key, content)
157
+ else:
158
+ # Binary (images): download β†’ re-upload
159
+ with tempfile.NamedTemporaryFile(delete=False) as tf:
160
+ tmp_path = tf.name
161
+ download_bucket_files(
162
+ BUCKET_ID,
163
+ files=[(old_key, tmp_path)],
164
+
165
+ )
166
+ with open(tmp_path, "rb") as f:
167
+ raw = f.read()
168
+ os.unlink(tmp_path)
169
+ batch_bucket_files(
170
+ BUCKET_ID,
171
+ add=[(raw, new_key)],
172
+
173
+ )
174
+
175
+ # Delete old key after successful copy
176
+ batch_bucket_files(
177
+ BUCKET_ID,
178
+ delete=[old_key],
179
+
180
+ )
181
+
182
+ except Exception as e:
183
+ raise HTTPException(
184
+ status_code=500,
185
+ detail=f"Failed to move bucket file '{old_key}': {str(e)}"
186
+ )
187
+
188
  save_cameras(data.user_id, cameras)
189
  return {"success": True, "updated": cam}
190
+
191
  if not camera_found:
192
  raise HTTPException(status_code=404, detail="Camera not found")
193
 
194
+
195
+ # ---------- DELETE CAMERA ----------
196
  @router.delete("/delete_camera")
197
  def delete_camera(user_id: str = Query(...), camera_name: str = Query(...)):
198
  if not user_exists(user_id):
199
  raise HTTPException(status_code=404, detail="User not found")
200
+
201
  cameras = load_cameras(user_id)
202
  new_list = [c for c in cameras if c["camera_name"].lower() != camera_name.lower()]
203
  if len(new_list) == len(cameras):
204
  raise HTTPException(status_code=404, detail="Camera not found")
205
+
206
+ # Delete all bucket files under this camera prefix
207
+ cam_prefix = _bucket_key(user_id, camera_name)
208
+ cam_files = _list_prefix(cam_prefix)
209
+
210
+ if cam_files:
211
+ keys_to_delete = [item.path for item in cam_files]
212
  try:
213
+ batch_bucket_files(
214
+ BUCKET_ID,
215
+ delete=keys_to_delete,
216
+
217
+ )
218
  except Exception as e:
219
+ raise HTTPException(
220
+ status_code=500,
221
+ detail=f"Failed to delete camera files from bucket: {str(e)}"
222
+ )
223
+
224
+ save_cameras(user_id, new_list)
225
+ return {"success": True, "deleted": camera_name}