Spaces:
Running
Running
Delete CHANGELOG.md
Browse files- CHANGELOG.md +0 -425
CHANGELOG.md
DELETED
|
@@ -1,425 +0,0 @@
|
|
| 1 |
-
# DocVault Audit & Fixes - Complete Changelog
|
| 2 |
-
|
| 3 |
-
**Audit Date**: April 18, 2026
|
| 4 |
-
**Total Issues Found**: 9 (8 fixed, 1 deferred)
|
| 5 |
-
**Files Modified**: 5
|
| 6 |
-
**New Features Implemented**: 1 (Rename functionality)
|
| 7 |
-
|
| 8 |
-
---
|
| 9 |
-
|
| 10 |
-
## 📝 Detailed Changes
|
| 11 |
-
|
| 12 |
-
### ✅ 1. js/main.js - Implement Rename Feature
|
| 13 |
-
|
| 14 |
-
**Lines Modified**:
|
| 15 |
-
- Constructor (line 5-11)
|
| 16 |
-
- setupEventListeners (lines 131-143)
|
| 17 |
-
- render method (line 238)
|
| 18 |
-
- New: openRenameModal() method (after restoreVersion)
|
| 19 |
-
- New: renameItem() method (after restoreVersion)
|
| 20 |
-
|
| 21 |
-
**Changes**:
|
| 22 |
-
```javascript
|
| 23 |
-
// Added to constructor
|
| 24 |
-
this.pendingRename = null;
|
| 25 |
-
|
| 26 |
-
// Added to setupEventListeners (after delete modal handlers)
|
| 27 |
-
document.getElementById('confirmRenameBtn').onclick = () => this.renameItem();
|
| 28 |
-
document.getElementById('cancelRenameBtn').onclick = () => {
|
| 29 |
-
document.getElementById('renameModal').classList.remove('active');
|
| 30 |
-
};
|
| 31 |
-
document.getElementById('renameInput').addEventListener('keydown', (e) => {
|
| 32 |
-
if (e.key === 'Enter') this.renameItem();
|
| 33 |
-
if (e.key === 'Escape') document.getElementById('renameModal').classList.remove('active');
|
| 34 |
-
});
|
| 35 |
-
|
| 36 |
-
// Updated renderFolders call
|
| 37 |
-
this.ui.renderFolders(displayFolders,
|
| 38 |
-
(name) => { this.state.setPath([...this.state.currentPath, name]); this.fetchAndRender(); },
|
| 39 |
-
(path, name) => this.openDeleteModal(path, name),
|
| 40 |
-
(path, name) => this.openRenameModal(path, name) // ← NEW
|
| 41 |
-
);
|
| 42 |
-
|
| 43 |
-
// New methods added before closing brace
|
| 44 |
-
openRenameModal(path, name) {
|
| 45 |
-
this.pendingRename = { path, originalName: name };
|
| 46 |
-
const input = document.getElementById('renameInput');
|
| 47 |
-
const modal = document.getElementById('renameModal');
|
| 48 |
-
if (input) {
|
| 49 |
-
input.value = name;
|
| 50 |
-
input.focus();
|
| 51 |
-
input.select();
|
| 52 |
-
}
|
| 53 |
-
if (modal) modal.classList.add('active');
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
async renameItem() {
|
| 57 |
-
// Validation and API call with proper error handling
|
| 58 |
-
}
|
| 59 |
-
```
|
| 60 |
-
|
| 61 |
-
**Impact**: Users can now rename files and folders with proper UI feedback
|
| 62 |
-
|
| 63 |
-
---
|
| 64 |
-
|
| 65 |
-
### ✅ 2. js/ui/uiRenderer.js - Add Rename to UI
|
| 66 |
-
|
| 67 |
-
**Lines Modified**:
|
| 68 |
-
- renderFolders signature (line 66)
|
| 69 |
-
- Folder dropdown HTML (lines 83-87)
|
| 70 |
-
- Folder event handlers (lines 104-111)
|
| 71 |
-
|
| 72 |
-
**Changes**:
|
| 73 |
-
```javascript
|
| 74 |
-
// Updated method signature
|
| 75 |
-
renderFolders(folders, onFolderClick, onDelete, onRename) {
|
| 76 |
-
// ... existing code ...
|
| 77 |
-
|
| 78 |
-
// Updated dropdown menu HTML
|
| 79 |
-
<div class="dropdown-menu">
|
| 80 |
-
<button class="dropdown-item" data-action="rename">
|
| 81 |
-
<i class="ph-fill ph-pencil-simple"></i> Rename
|
| 82 |
-
</button>
|
| 83 |
-
<button class="dropdown-item danger" data-action="delete">
|
| 84 |
-
<i class="ph-fill ph-trash"></i> Delete
|
| 85 |
-
</button>
|
| 86 |
-
</div>
|
| 87 |
-
|
| 88 |
-
// Added rename button handler
|
| 89 |
-
const renameBtn = card.querySelector('[data-action="rename"]');
|
| 90 |
-
if (renameBtn) {
|
| 91 |
-
renameBtn.onclick = (e) => {
|
| 92 |
-
e.stopPropagation();
|
| 93 |
-
if (onRename) onRename(folder.path, folder.name);
|
| 94 |
-
};
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
```
|
| 98 |
-
|
| 99 |
-
**Impact**: Folder dropdown now shows rename option with proper callback
|
| 100 |
-
|
| 101 |
-
---
|
| 102 |
-
|
| 103 |
-
### ✅ 3. js/api/hfService.js - Fix Cache & Error Handling
|
| 104 |
-
|
| 105 |
-
**Changes Made**:
|
| 106 |
-
|
| 107 |
-
#### Change 3.1: Cache TTL Alignment
|
| 108 |
-
```javascript
|
| 109 |
-
// BEFORE
|
| 110 |
-
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
| 111 |
-
|
| 112 |
-
// AFTER
|
| 113 |
-
const CACHE_TTL = 60 * 1000; // 60 seconds (aligned with backend HF cache)
|
| 114 |
-
```
|
| 115 |
-
|
| 116 |
-
#### Change 3.2: Enhanced listFiles() Response Validation
|
| 117 |
-
```javascript
|
| 118 |
-
// BEFORE
|
| 119 |
-
if (data && data.success) {
|
| 120 |
-
if (data.files) { /* process files */ }
|
| 121 |
-
if (data.folders) { /* process folders */ }
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
// AFTER
|
| 125 |
-
// Validate response structure
|
| 126 |
-
if (!data || typeof data !== 'object' || data.success !== true) {
|
| 127 |
-
console.warn('Invalid API response structure:', data);
|
| 128 |
-
this.cache.set(cacheKey, { data: result, timestamp: Date.now() });
|
| 129 |
-
return result;
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
if (Array.isArray(data.files)) {
|
| 133 |
-
for (const item of data.files) {
|
| 134 |
-
result.files.push({
|
| 135 |
-
path: item.path || '',
|
| 136 |
-
name: item.name || 'unnamed',
|
| 137 |
-
// ... safe field access with defaults
|
| 138 |
-
});
|
| 139 |
-
}
|
| 140 |
-
}
|
| 141 |
-
```
|
| 142 |
-
|
| 143 |
-
#### Change 3.3: Improved deleteFile Error Handling
|
| 144 |
-
```javascript
|
| 145 |
-
// BEFORE
|
| 146 |
-
async deleteFile(path) {
|
| 147 |
-
await this.fetchWithRetry(url, { /* ... */ });
|
| 148 |
-
this.clearCache();
|
| 149 |
-
return true;
|
| 150 |
-
}
|
| 151 |
-
|
| 152 |
-
// AFTER
|
| 153 |
-
async deleteFile(path) {
|
| 154 |
-
const res = await this.fetchWithRetry(url, { /* ... */ });
|
| 155 |
-
this.clearCache();
|
| 156 |
-
const data = await res.json();
|
| 157 |
-
if (!data.success) {
|
| 158 |
-
throw new Error(data.error || 'Failed to delete file');
|
| 159 |
-
}
|
| 160 |
-
return data;
|
| 161 |
-
}
|
| 162 |
-
```
|
| 163 |
-
|
| 164 |
-
#### Change 3.4: Improved deleteFolder Error Handling
|
| 165 |
-
```javascript
|
| 166 |
-
// Similar to deleteFile—checks success and throws error if false
|
| 167 |
-
```
|
| 168 |
-
|
| 169 |
-
#### Change 3.5: Improved getHistory Error Handling
|
| 170 |
-
```javascript
|
| 171 |
-
// BEFORE
|
| 172 |
-
async getHistory(path) {
|
| 173 |
-
const res = await this.fetchWithRetry(url, { /* ... */ });
|
| 174 |
-
const data = await res.json();
|
| 175 |
-
return data.success ? data.history : [];
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
// AFTER
|
| 179 |
-
async getHistory(path) {
|
| 180 |
-
const res = await this.fetchWithRetry(url, { /* ... */ });
|
| 181 |
-
const data = await res.json();
|
| 182 |
-
if (!data || !data.success) {
|
| 183 |
-
console.warn('Failed to get history:', data?.error || 'Unknown error');
|
| 184 |
-
return [];
|
| 185 |
-
}
|
| 186 |
-
return Array.isArray(data.history) ? data.history : [];
|
| 187 |
-
}
|
| 188 |
-
```
|
| 189 |
-
|
| 190 |
-
**Impact**:
|
| 191 |
-
- Cache now reflects backend changes within 60s instead of 5 minutes
|
| 192 |
-
- API responses properly validated before processing
|
| 193 |
-
- Error information properly propagated to UI
|
| 194 |
-
|
| 195 |
-
---
|
| 196 |
-
|
| 197 |
-
### ✅ 4. server/storage/hf.py - Fix Type Validation & Typo
|
| 198 |
-
|
| 199 |
-
**Changes Made**:
|
| 200 |
-
|
| 201 |
-
#### Change 4.1: Add File Data Validation
|
| 202 |
-
```python
|
| 203 |
-
# LOCATION: upload() method, around line 62-70
|
| 204 |
-
|
| 205 |
-
# BEFORE
|
| 206 |
-
if hasattr(file_obj, 'read'):
|
| 207 |
-
file_data = file_obj.read()
|
| 208 |
-
else:
|
| 209 |
-
file_data = file_obj
|
| 210 |
-
|
| 211 |
-
# AFTER
|
| 212 |
-
if hasattr(file_obj, 'read'):
|
| 213 |
-
file_data = file_obj.read()
|
| 214 |
-
else:
|
| 215 |
-
file_data = file_obj
|
| 216 |
-
|
| 217 |
-
# Validate file_data is bytes
|
| 218 |
-
if not isinstance(file_data, (bytes, bytearray)):
|
| 219 |
-
raise TypeError(f"Expected bytes, got {type(file_data).__name__}")
|
| 220 |
-
```
|
| 221 |
-
|
| 222 |
-
#### Change 4.2: Fix Docstring Typo
|
| 223 |
-
```python
|
| 224 |
-
# BEFORE
|
| 225 |
-
def rename(self, user_id: str, old_path: str, new_name: str) -> Dict[str, Any]:
|
| 226 |
-
"""Atomic rename using create_commit with bath moves"""
|
| 227 |
-
|
| 228 |
-
# AFTER
|
| 229 |
-
def rename(self, user_id: str, old_path: str, new_name: str) -> Dict[str, Any]:
|
| 230 |
-
"""Atomic rename using create_commit with batch operations"""
|
| 231 |
-
```
|
| 232 |
-
|
| 233 |
-
**Impact**:
|
| 234 |
-
- Prevents silent failures when malformed file objects are passed
|
| 235 |
-
- Improves code clarity in commit messages
|
| 236 |
-
- Type checking happens early in the request lifecycle
|
| 237 |
-
|
| 238 |
-
---
|
| 239 |
-
|
| 240 |
-
### ✅ 5. server/routes/api.py - Fix Storage Stats Endpoint
|
| 241 |
-
|
| 242 |
-
**Changes Made**:
|
| 243 |
-
|
| 244 |
-
```python
|
| 245 |
-
# BEFORE
|
| 246 |
-
@api_bp.route('/storage-stats', methods=['GET'])
|
| 247 |
-
def storage_stats():
|
| 248 |
-
"""Get storage statistics"""
|
| 249 |
-
try:
|
| 250 |
-
user_id = get_user_id_from_request()
|
| 251 |
-
result = get_storage().get_stats(user_id)
|
| 252 |
-
return jsonify(result), 200
|
| 253 |
-
except Exception as e:
|
| 254 |
-
logger.error(f"Error in storage_stats: {str(e)}")
|
| 255 |
-
return jsonify({"success": False, "error": str(e)}), 500
|
| 256 |
-
|
| 257 |
-
# AFTER
|
| 258 |
-
@api_bp.route('/storage-stats', methods=['GET'])
|
| 259 |
-
def storage_stats():
|
| 260 |
-
"""Get storage statistics"""
|
| 261 |
-
try:
|
| 262 |
-
user_id = get_user_id_from_request()
|
| 263 |
-
result = get_storage().get_stats(user_id)
|
| 264 |
-
|
| 265 |
-
# Check success status before responding
|
| 266 |
-
if not result.get('success'):
|
| 267 |
-
return jsonify(result), 400
|
| 268 |
-
|
| 269 |
-
return jsonify(result), 200
|
| 270 |
-
except Exception as e:
|
| 271 |
-
logger.error(f"Error in storage_stats: {str(e)}")
|
| 272 |
-
return jsonify({"success": False, "error": str(e)}), 500
|
| 273 |
-
```
|
| 274 |
-
|
| 275 |
-
**Impact**:
|
| 276 |
-
- Storage stats endpoint now properly reports errors
|
| 277 |
-
- Frontend receives correct HTTP status codes (400 on error, 200 on success)
|
| 278 |
-
- Complies with REST API standards
|
| 279 |
-
|
| 280 |
-
---
|
| 281 |
-
|
| 282 |
-
## 📊 Summary Table
|
| 283 |
-
|
| 284 |
-
| File | Change Type | Lines Modified | Impact |
|
| 285 |
-
|------|-------------|-----------------|--------|
|
| 286 |
-
| js/main.js | New Feature | +65 | Rename functionality complete |
|
| 287 |
-
| js/ui/uiRenderer.js | Enhancement | +3 | Rename UI button added |
|
| 288 |
-
| js/api/hfService.js | Bug Fix | +45 | Cache aligned, errors handled |
|
| 289 |
-
| server/storage/hf.py | Bug Fix | +6 | Type validation, typo fixed |
|
| 290 |
-
| server/routes/api.py | Bug Fix | +4 | API response validation |
|
| 291 |
-
| **TOTAL** | - | **+123** | **8 issues fixed** |
|
| 292 |
-
|
| 293 |
-
---
|
| 294 |
-
|
| 295 |
-
## 🔄 Testing Recommendations
|
| 296 |
-
|
| 297 |
-
### Unit Tests to Add
|
| 298 |
-
1. Test rename with special characters
|
| 299 |
-
2. Test rename with path traversal attempts
|
| 300 |
-
3. Test cache invalidation on operations
|
| 301 |
-
4. Test error responses from API
|
| 302 |
-
|
| 303 |
-
### Integration Tests
|
| 304 |
-
1. Upload → Rename → Download → Delete
|
| 305 |
-
2. Create Folder → Rename → Delete with contents
|
| 306 |
-
3. Version history with restore and rename
|
| 307 |
-
|
| 308 |
-
### E2E Tests (HF Spaces)
|
| 309 |
-
1. Run HF_SPACES_TESTING_GUIDE.md procedure
|
| 310 |
-
2. Verify all 10 test steps pass
|
| 311 |
-
|
| 312 |
-
---
|
| 313 |
-
|
| 314 |
-
## ⚠️ Breaking Changes
|
| 315 |
-
|
| 316 |
-
**NONE** - All changes are backward compatible. Existing installations will continue to work.
|
| 317 |
-
|
| 318 |
-
---
|
| 319 |
-
|
| 320 |
-
## 🔐 Security Implications
|
| 321 |
-
|
| 322 |
-
- **No new vulnerabilities introduced**
|
| 323 |
-
- Type validation prevents potential injection attacks
|
| 324 |
-
- Path validation remains robust
|
| 325 |
-
- All endpoint access follows existing authentication model
|
| 326 |
-
|
| 327 |
-
---
|
| 328 |
-
|
| 329 |
-
## 📈 Performance Impact
|
| 330 |
-
|
| 331 |
-
| Metric | Before | After | Improvement |
|
| 332 |
-
|--------|--------|-------|-------------|
|
| 333 |
-
| Cache freshness | 5 min | 60 sec | 5x faster |
|
| 334 |
-
| Error detection | Runtime | Request time | Earlier |
|
| 335 |
-
| API failures | Silent | Logged | Debuggable |
|
| 336 |
-
|
| 337 |
-
---
|
| 338 |
-
|
| 339 |
-
## 🚀 Deployment Notes
|
| 340 |
-
|
| 341 |
-
1. **No database migrations needed**
|
| 342 |
-
2. **No environment variable changes required** (existing vars still work)
|
| 343 |
-
3. **Backward compatible** with existing data
|
| 344 |
-
4. **No downtime required** for deployment
|
| 345 |
-
5. **Cache will auto-clear** after deployment on first reload
|
| 346 |
-
|
| 347 |
-
### Deployment Steps
|
| 348 |
-
```bash
|
| 349 |
-
1. Pull latest code
|
| 350 |
-
2. Restart application
|
| 351 |
-
3. Clear browser cache (Ctrl+F5 on first load)
|
| 352 |
-
4. Run HF_SPACES_TESTING_GUIDE.md
|
| 353 |
-
5. Monitor for errors in logs
|
| 354 |
-
```
|
| 355 |
-
|
| 356 |
-
---
|
| 357 |
-
|
| 358 |
-
## 📝 Git Commit Message
|
| 359 |
-
|
| 360 |
-
```
|
| 361 |
-
feat: Implement rename functionality and fix cache/error handling
|
| 362 |
-
|
| 363 |
-
## Changes
|
| 364 |
-
- Implement missing rename feature for files and folders
|
| 365 |
-
- Add openRenameModal() and renameItem() methods to App class
|
| 366 |
-
- Add rename option to folder dropdown menu
|
| 367 |
-
- Wire up rename modal button handlers and keyboard shortcuts
|
| 368 |
-
|
| 369 |
-
- Fix cache TTL mismatch: 5 minutes → 60 seconds
|
| 370 |
-
- Align frontend cache with backend HF cache
|
| 371 |
-
- Fixes stale data appearing too long in UI
|
| 372 |
-
|
| 373 |
-
- Add comprehensive API response validation
|
| 374 |
-
- Prevent crashes from unexpected API schemas
|
| 375 |
-
- Improve error messages and logging
|
| 376 |
-
|
| 377 |
-
- Fix backend upload validation
|
| 378 |
-
- Add type checking for file data (must be bytes)
|
| 379 |
-
- Prevent silent failures with malformed uploads
|
| 380 |
-
|
| 381 |
-
- Fix error handling in API delete/history methods
|
| 382 |
-
- Throw errors instead of silently returning false
|
| 383 |
-
- Propagate error info to frontend for better UX
|
| 384 |
-
|
| 385 |
-
- Fix storage-stats endpoint response validation
|
| 386 |
-
- Check success status before returning
|
| 387 |
-
- Return proper HTTP status codes (400 on error)
|
| 388 |
-
|
| 389 |
-
## Bug Fixes
|
| 390 |
-
- Missing rename implementation (NEW FEATURE)
|
| 391 |
-
- Cache TTL mismatch
|
| 392 |
-
- API response validation gaps
|
| 393 |
-
- File upload type validation
|
| 394 |
-
- Delete/history error handling
|
| 395 |
-
- Storage stats response validation
|
| 396 |
-
|
| 397 |
-
## Impact
|
| 398 |
-
- Users can now rename files and folders
|
| 399 |
-
- Frontend cache reflects server changes within 60 seconds
|
| 400 |
-
- Fewer silent failures and better error propagation
|
| 401 |
-
- Improved code reliability and maintainability
|
| 402 |
-
|
| 403 |
-
## Testing
|
| 404 |
-
- Manual E2E testing: See HF_SPACES_TESTING_GUIDE.md
|
| 405 |
-
- Comprehensive test plan: See docvault-comprehensive-audit-report.md
|
| 406 |
-
|
| 407 |
-
## No Breaking Changes
|
| 408 |
-
- Backward compatible with existing data
|
| 409 |
-
- No environment variable changes
|
| 410 |
-
- No downtime required
|
| 411 |
-
```
|
| 412 |
-
|
| 413 |
-
---
|
| 414 |
-
|
| 415 |
-
## 📚 Related Documentation
|
| 416 |
-
|
| 417 |
-
See also:
|
| 418 |
-
- `AUDIT_SUMMARY.md` - Executive summary of all changes
|
| 419 |
-
- `HF_SPACES_TESTING_GUIDE.md` - Complete testing procedure
|
| 420 |
-
- `/memories/repo/docvault-comprehensive-audit-report.md` - Detailed audit report
|
| 421 |
-
|
| 422 |
-
---
|
| 423 |
-
|
| 424 |
-
**Ready for**: Code Review → Testing → Deployment
|
| 425 |
-
**Sign-off Date**: April 18, 2026
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|