@gallop.software/studio 2.3.32 → 2.3.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
12
12
|
}
|
|
13
13
|
</style>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-DZ7F7UJh.js"></script>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
package/dist/server/index.js
CHANGED
|
@@ -412,6 +412,18 @@ function jsonResponse(data, init) {
|
|
|
412
412
|
headers
|
|
413
413
|
});
|
|
414
414
|
}
|
|
415
|
+
function streamResponse(stream, init) {
|
|
416
|
+
const headers = new Headers({
|
|
417
|
+
"Content-Type": "text/event-stream",
|
|
418
|
+
"Cache-Control": "no-cache",
|
|
419
|
+
Connection: "keep-alive",
|
|
420
|
+
...init?.headers
|
|
421
|
+
});
|
|
422
|
+
return new Response(stream, {
|
|
423
|
+
status: 200,
|
|
424
|
+
headers
|
|
425
|
+
});
|
|
426
|
+
}
|
|
415
427
|
|
|
416
428
|
// src/handlers/list.ts
|
|
417
429
|
function getExistingThumbnails(originalPath, entry) {
|
|
@@ -1395,6 +1407,258 @@ async function handleRename(request) {
|
|
|
1395
1407
|
return jsonResponse({ error: "Failed to rename" }, { status: 500 });
|
|
1396
1408
|
}
|
|
1397
1409
|
}
|
|
1410
|
+
async function handleRenameStream(request) {
|
|
1411
|
+
const encoder = new TextEncoder();
|
|
1412
|
+
const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\/$/, "");
|
|
1413
|
+
const stream = new ReadableStream({
|
|
1414
|
+
async start(controller) {
|
|
1415
|
+
const sendEvent = (data) => {
|
|
1416
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
1417
|
+
|
|
1418
|
+
`));
|
|
1419
|
+
};
|
|
1420
|
+
try {
|
|
1421
|
+
const { oldPath, newName } = await request.json();
|
|
1422
|
+
if (!oldPath || !newName) {
|
|
1423
|
+
sendEvent({ type: "error", message: "Path and new name are required" });
|
|
1424
|
+
controller.close();
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
const safePath = oldPath.replace(/\.\./g, "");
|
|
1428
|
+
const absoluteOldPath = getWorkspacePath(safePath);
|
|
1429
|
+
if (!absoluteOldPath.startsWith(getPublicPath())) {
|
|
1430
|
+
sendEvent({ type: "error", message: "Invalid path" });
|
|
1431
|
+
controller.close();
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const oldRelativePath = safePath.replace(/^public\//, "");
|
|
1435
|
+
const isImagePath = isImageFile(path6.basename(oldPath));
|
|
1436
|
+
let hasLocalItem = false;
|
|
1437
|
+
let isFile = true;
|
|
1438
|
+
try {
|
|
1439
|
+
const stats = await fs6.stat(absoluteOldPath);
|
|
1440
|
+
hasLocalItem = true;
|
|
1441
|
+
isFile = stats.isFile();
|
|
1442
|
+
} catch {
|
|
1443
|
+
const meta2 = await loadMeta();
|
|
1444
|
+
const oldKey2 = "/" + oldRelativePath;
|
|
1445
|
+
const entry2 = meta2[oldKey2];
|
|
1446
|
+
if (!entry2) {
|
|
1447
|
+
sendEvent({ type: "error", message: "File or folder not found" });
|
|
1448
|
+
controller.close();
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
isFile = true;
|
|
1452
|
+
}
|
|
1453
|
+
const sanitizedName = isFile ? slugifyFilename(newName) : slugifyFolderName(newName);
|
|
1454
|
+
if (!sanitizedName) {
|
|
1455
|
+
sendEvent({ type: "error", message: "Invalid name" });
|
|
1456
|
+
controller.close();
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
const parentDir = path6.dirname(absoluteOldPath);
|
|
1460
|
+
const absoluteNewPath = path6.join(parentDir, sanitizedName);
|
|
1461
|
+
const newRelativePath = path6.join(path6.dirname(oldRelativePath), sanitizedName);
|
|
1462
|
+
const newPath = path6.join(path6.dirname(safePath), sanitizedName);
|
|
1463
|
+
const meta = await loadMeta();
|
|
1464
|
+
const cdnUrls = getCdnUrls(meta);
|
|
1465
|
+
if (isFile) {
|
|
1466
|
+
const newKey2 = "/" + newRelativePath;
|
|
1467
|
+
if (meta[newKey2]) {
|
|
1468
|
+
sendEvent({ type: "error", message: "An item with this name already exists" });
|
|
1469
|
+
controller.close();
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
try {
|
|
1474
|
+
await fs6.access(absoluteNewPath);
|
|
1475
|
+
sendEvent({ type: "error", message: "An item with this name already exists" });
|
|
1476
|
+
controller.close();
|
|
1477
|
+
return;
|
|
1478
|
+
} catch {
|
|
1479
|
+
}
|
|
1480
|
+
if (!isFile) {
|
|
1481
|
+
const oldPrefix = "/" + oldRelativePath + "/";
|
|
1482
|
+
const newPrefix = "/" + newRelativePath + "/";
|
|
1483
|
+
const itemsToUpdate = [];
|
|
1484
|
+
for (const [key, entry2] of Object.entries(meta)) {
|
|
1485
|
+
if (key.startsWith(oldPrefix) && entry2 && typeof entry2 === "object") {
|
|
1486
|
+
const newKey2 = key.replace(oldPrefix, newPrefix);
|
|
1487
|
+
itemsToUpdate.push({ oldKey: key, newKey: newKey2, entry: entry2 });
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
const total = itemsToUpdate.length + 1;
|
|
1491
|
+
sendEvent({ type: "start", total, message: `Renaming folder with ${itemsToUpdate.length} item(s)...` });
|
|
1492
|
+
if (hasLocalItem) {
|
|
1493
|
+
await fs6.rename(absoluteOldPath, absoluteNewPath);
|
|
1494
|
+
}
|
|
1495
|
+
sendEvent({ type: "progress", current: 1, total, renamed: 1, message: "Renamed folder" });
|
|
1496
|
+
let renamed = 1;
|
|
1497
|
+
for (const item of itemsToUpdate) {
|
|
1498
|
+
const { oldKey: oldKey2, newKey: newKey2, entry: entry2 } = item;
|
|
1499
|
+
const isInCloud2 = entry2.c !== void 0;
|
|
1500
|
+
const fileCdnUrl2 = isInCloud2 ? cdnUrls[entry2.c] : void 0;
|
|
1501
|
+
const isInOurR22 = isInCloud2 && fileCdnUrl2 === publicUrl;
|
|
1502
|
+
const hasThumbnails2 = isProcessed(entry2);
|
|
1503
|
+
if (isInOurR22) {
|
|
1504
|
+
const localFilePath = getPublicPath(newKey2);
|
|
1505
|
+
let hasLocalFile = false;
|
|
1506
|
+
try {
|
|
1507
|
+
await fs6.access(localFilePath);
|
|
1508
|
+
hasLocalFile = true;
|
|
1509
|
+
} catch {
|
|
1510
|
+
}
|
|
1511
|
+
if (hasLocalFile) {
|
|
1512
|
+
await deleteFromCdn(oldKey2, hasThumbnails2);
|
|
1513
|
+
await uploadOriginalToCdn(newKey2);
|
|
1514
|
+
if (hasThumbnails2) {
|
|
1515
|
+
await uploadToCdn(newKey2);
|
|
1516
|
+
}
|
|
1517
|
+
try {
|
|
1518
|
+
await fs6.unlink(localFilePath);
|
|
1519
|
+
} catch {
|
|
1520
|
+
}
|
|
1521
|
+
if (hasThumbnails2) {
|
|
1522
|
+
await deleteLocalThumbnails(newKey2);
|
|
1523
|
+
}
|
|
1524
|
+
} else {
|
|
1525
|
+
try {
|
|
1526
|
+
const buffer = await downloadFromCdn(oldKey2);
|
|
1527
|
+
await fs6.mkdir(path6.dirname(localFilePath), { recursive: true });
|
|
1528
|
+
await fs6.writeFile(localFilePath, buffer);
|
|
1529
|
+
if (hasThumbnails2) {
|
|
1530
|
+
const oldThumbPaths = getAllThumbnailPaths(oldKey2);
|
|
1531
|
+
const newThumbPaths = getAllThumbnailPaths(newKey2);
|
|
1532
|
+
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
1533
|
+
try {
|
|
1534
|
+
const thumbBuffer = await downloadFromCdn(oldThumbPaths[i]);
|
|
1535
|
+
const newThumbLocalPath = getPublicPath(newThumbPaths[i]);
|
|
1536
|
+
await fs6.mkdir(path6.dirname(newThumbLocalPath), { recursive: true });
|
|
1537
|
+
await fs6.writeFile(newThumbLocalPath, thumbBuffer);
|
|
1538
|
+
} catch {
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
await deleteFromCdn(oldKey2, hasThumbnails2);
|
|
1543
|
+
await uploadOriginalToCdn(newKey2);
|
|
1544
|
+
if (hasThumbnails2) {
|
|
1545
|
+
await uploadToCdn(newKey2);
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
await fs6.unlink(localFilePath);
|
|
1549
|
+
} catch {
|
|
1550
|
+
}
|
|
1551
|
+
if (hasThumbnails2) {
|
|
1552
|
+
await deleteLocalThumbnails(newKey2);
|
|
1553
|
+
}
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
console.error(`Failed to re-upload ${oldKey2}:`, err);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
delete meta[oldKey2];
|
|
1560
|
+
meta[newKey2] = entry2;
|
|
1561
|
+
renamed++;
|
|
1562
|
+
sendEvent({
|
|
1563
|
+
type: "progress",
|
|
1564
|
+
current: renamed,
|
|
1565
|
+
total,
|
|
1566
|
+
renamed,
|
|
1567
|
+
message: `Renamed ${path6.basename(newKey2)}`
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
await saveMeta(meta);
|
|
1571
|
+
await deleteEmptyFolders(getPublicPath());
|
|
1572
|
+
sendEvent({ type: "complete", renamed, newPath });
|
|
1573
|
+
controller.close();
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const oldKey = "/" + oldRelativePath;
|
|
1577
|
+
const newKey = "/" + newRelativePath;
|
|
1578
|
+
const entry = meta[oldKey];
|
|
1579
|
+
const isInCloud = entry?.c !== void 0;
|
|
1580
|
+
const fileCdnUrl = isInCloud && entry?.c !== void 0 ? cdnUrls[entry.c] : void 0;
|
|
1581
|
+
const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;
|
|
1582
|
+
const hasThumbnails = entry ? isProcessed(entry) : false;
|
|
1583
|
+
sendEvent({ type: "start", total: 1, message: "Renaming file..." });
|
|
1584
|
+
if (isInOurR2 && !hasLocalItem && isImagePath) {
|
|
1585
|
+
const buffer = await downloadFromCdn(oldKey);
|
|
1586
|
+
await fs6.mkdir(path6.dirname(absoluteNewPath), { recursive: true });
|
|
1587
|
+
await fs6.writeFile(absoluteNewPath, buffer);
|
|
1588
|
+
if (hasThumbnails) {
|
|
1589
|
+
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1590
|
+
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1591
|
+
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
1592
|
+
try {
|
|
1593
|
+
const thumbBuffer = await downloadFromCdn(oldThumbPaths[i]);
|
|
1594
|
+
const newThumbLocalPath = getPublicPath(newThumbPaths[i]);
|
|
1595
|
+
await fs6.mkdir(path6.dirname(newThumbLocalPath), { recursive: true });
|
|
1596
|
+
await fs6.writeFile(newThumbLocalPath, thumbBuffer);
|
|
1597
|
+
} catch {
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
await deleteFromCdn(oldKey, hasThumbnails);
|
|
1602
|
+
await uploadOriginalToCdn(newKey);
|
|
1603
|
+
if (hasThumbnails) {
|
|
1604
|
+
await uploadToCdn(newKey);
|
|
1605
|
+
}
|
|
1606
|
+
try {
|
|
1607
|
+
await fs6.unlink(absoluteNewPath);
|
|
1608
|
+
} catch {
|
|
1609
|
+
}
|
|
1610
|
+
if (hasThumbnails) {
|
|
1611
|
+
await deleteLocalThumbnails(newKey);
|
|
1612
|
+
}
|
|
1613
|
+
delete meta[oldKey];
|
|
1614
|
+
if (entry) meta[newKey] = entry;
|
|
1615
|
+
await saveMeta(meta);
|
|
1616
|
+
sendEvent({ type: "complete", renamed: 1, newPath });
|
|
1617
|
+
controller.close();
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
if (hasLocalItem) {
|
|
1621
|
+
await fs6.rename(absoluteOldPath, absoluteNewPath);
|
|
1622
|
+
}
|
|
1623
|
+
if (isImagePath && entry) {
|
|
1624
|
+
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1625
|
+
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1626
|
+
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
1627
|
+
const oldThumbPath = getPublicPath(oldThumbPaths[i]);
|
|
1628
|
+
const newThumbPath = getPublicPath(newThumbPaths[i]);
|
|
1629
|
+
await fs6.mkdir(path6.dirname(newThumbPath), { recursive: true });
|
|
1630
|
+
try {
|
|
1631
|
+
await fs6.rename(oldThumbPath, newThumbPath);
|
|
1632
|
+
} catch {
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (isInOurR2) {
|
|
1636
|
+
await deleteFromCdn(oldKey, hasThumbnails);
|
|
1637
|
+
await uploadOriginalToCdn(newKey);
|
|
1638
|
+
if (hasThumbnails) {
|
|
1639
|
+
await uploadToCdn(newKey);
|
|
1640
|
+
}
|
|
1641
|
+
try {
|
|
1642
|
+
await fs6.unlink(absoluteNewPath);
|
|
1643
|
+
} catch {
|
|
1644
|
+
}
|
|
1645
|
+
await deleteLocalThumbnails(newKey);
|
|
1646
|
+
}
|
|
1647
|
+
delete meta[oldKey];
|
|
1648
|
+
meta[newKey] = entry;
|
|
1649
|
+
await saveMeta(meta);
|
|
1650
|
+
}
|
|
1651
|
+
sendEvent({ type: "complete", renamed: 1, newPath });
|
|
1652
|
+
controller.close();
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
console.error("Rename stream error:", error);
|
|
1655
|
+
sendEvent({ type: "error", message: "Failed to rename" });
|
|
1656
|
+
controller.close();
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
return streamResponse(stream);
|
|
1661
|
+
}
|
|
1398
1662
|
async function handleMoveStream(request) {
|
|
1399
1663
|
const encoder = new TextEncoder();
|
|
1400
1664
|
const stream = new ReadableStream({
|
|
@@ -2996,6 +3260,7 @@ async function startServer(options) {
|
|
|
2996
3260
|
app.post("/api/studio/upload", wrapRawHandler(handleUpload));
|
|
2997
3261
|
app.post("/api/studio/create-folder", wrapHandler(handleCreateFolder));
|
|
2998
3262
|
app.post("/api/studio/rename", wrapHandler(handleRename));
|
|
3263
|
+
app.post("/api/studio/rename-stream", wrapHandler(handleRenameStream, true));
|
|
2999
3264
|
app.post("/api/studio/move", wrapHandler(handleMoveStream, true));
|
|
3000
3265
|
app.post("/api/studio/sync", wrapHandler(handleSync, true));
|
|
3001
3266
|
app.post("/api/studio/reprocess-stream", wrapHandler(handleReprocessStream, true));
|