Merge pull request #114 from 11cafe/py-async

offload py server file io to another thread to reduce impact on main …
This commit is contained in:
Weixuan Fu
2024-01-25 02:20:45 +08:00
committed by GitHub
7 changed files with 76 additions and 98 deletions
+40 -59
View File
@@ -1,3 +1,4 @@
import asyncio
import server
from aiohttp import web
import aiohttp
@@ -43,16 +44,16 @@ async def save_db(request):
json_data = data['json']
file_name = f'{db_dir_path}/{table}.json'
if not os.path.exists(db_dir_path):
os.makedirs(db_dir_path)
# Write the JSON data to the specified file
with open(file_name, 'w') as file:
file.write(json.dumps(json_data, indent=4))
# Offload file writing to a separate thread
def write_json_string_to_db(file_name, json_data):
if not os.path.exists(db_dir_path):
os.makedirs(db_dir_path)
# Write the JSON data to the specified file
with open(file_name, 'w') as file:
file.write(json.dumps(json_data, indent=4))
await asyncio.to_thread(write_json_string_to_db, file_name, json_data)
return web.Response(text=f"JSON saved to {file_name}")
def read_table(table):
if not table:
return None
@@ -69,44 +70,12 @@ def read_table(table):
async def get_workspace(request):
# Extract the table parameter from the query string
table = request.query.get('table')
data = read_table(table)
data = await asyncio.to_thread(read_table, table)
return web.json_response(data)
BACKUP_DIR = os.path.join(workspace_path, "backup")
MAX_BACKUP_FILES = 20
@server.PromptServer.instance.routes.post("/workspace/save_backup")
async def save_backup(request):
try:
data = await request.json()
file_path = data.get('file_path')
json_str = data.get('json_str')
file_path = os.path.join(BACKUP_DIR, file_path)
if not file_path or not json_str:
return web.Response(text=json.dumps({"error": "file_path and json_str are required"}), status=400)
directory = os.path.dirname(file_path)
# Create the directory if it does not exist
os.makedirs(directory, exist_ok=True)
with open(file_path, 'w') as file:
file.write(json_str)
# Check the number of files in the directory after writing the new file
files = [f for f in os.listdir(directory) if os.path.isfile(
os.path.join(directory, f))]
if len(files) > MAX_BACKUP_FILES:
# Find the oldest file (smallest filename)
oldest_file = min(files, key=lambda x: x)
# Delete the oldest file
os.remove(os.path.join(directory, oldest_file))
return web.Response(text=json.dumps({"message": "File saved successfully"}), status=200)
except Exception as e:
return web.Response(text=json.dumps({"error": str(e)}), status=500)
@server.PromptServer.instance.routes.post("/workspace/list_backup")
async def list_backup(request):
try:
@@ -170,13 +139,17 @@ async def update_file(request):
data = await request.json()
file_path = data['file_path']
json_str = data['json_str']
my_workflows_dir = get_my_workflows_dir()
full_path = os.path.join(my_workflows_dir, file_path)
# Create the directory if it doesn't exist
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'w', encoding='utf-8') as file:
file.write(json_str)
def write_json_to_file(json_str):
my_workflows_dir = get_my_workflows_dir()
full_path = os.path.join(my_workflows_dir, file_path)
# Create the directory if it doesn't exist
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'w', encoding='utf-8') as file:
file.write(json_str)
# Offload the file update to a separate thread
await asyncio.to_thread(write_json_to_file, json_str)
return web.Response(text="File updated successfully")
@@ -185,22 +158,30 @@ async def delete_file(request):
data = await request.json()
file_path = data['file_path']
delete_empty_folder = data['deleteEmptyFolder']
my_workflows_dir = get_my_workflows_dir()
full_path = os.path.join(my_workflows_dir, file_path)
if os.path.exists(full_path):
os.remove(full_path)
def sync_delete_file(file_path, delete_empty_folder):
my_workflows_dir = get_my_workflows_dir()
full_path = os.path.join(my_workflows_dir, file_path)
# Check if the directory is empty after deleting the file
directory = os.path.dirname(full_path)
if delete_empty_folder and not os.listdir(directory):
# If the directory is empty, remove the directory
os.rmdir(directory)
return web.Response(text="File and empty directory deleted successfully")
if os.path.exists(full_path):
os.remove(full_path)
directory = os.path.dirname(full_path)
if delete_empty_folder and not os.listdir(directory):
# If the directory is empty, remove the directory
os.rmdir(directory)
return "File and empty directory deleted successfully"
else:
return "File deleted successfully"
else:
return web.Response(text="File deleted successfully")
return "File not found"
# Run the synchronous file operation in a separate thread
response_text = await asyncio.to_thread(sync_delete_file, file_path, delete_empty_folder)
if response_text == "File not found":
return web.Response(text=response_text, status=404)
else:
return web.Response(text="File not found", status=404)
return web.Response(text=response_text)
@server.PromptServer.instance.routes.post("/workspace/rename_file")
-21
View File
@@ -17,8 +17,6 @@ export async function getDB(table: Table): Promise<string | undefined> {
}
export async function saveDB(table: Table, jsonData: string) {
// const tableBackupFile = table + "/" + Date.now() + ".json";
// saveBackup(tableBackupFile, jsonData);
try {
const response = await fetch("/workspace/save_db", {
method: "POST",
@@ -73,25 +71,6 @@ export async function deleteFile(file_path: string, deleteEmptyFolder = false) {
}
}
export async function saveBackup(file_path: string, jsonData: string) {
try {
const response = await fetch("/workspace/save_backup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
file_path: file_path,
json_str: jsonData,
}),
});
const result = await response.text();
return result;
} catch (error) {
console.error("Error saving workspace backup:", error);
}
}
export async function listBackup(dir: string) {
try {
const response = await fetch("/workspace/list_backup", {
+21 -10
View File
@@ -91,11 +91,16 @@ export default function App() {
}
};
const setCurFlowIDAndName = (id: string, name: string) => {
const setCurFlowIDAndName = (id: string | null, name: string) => {
// curID null is when you deleted current workflow
curFlowID.current = id;
setFlowID(id);
setCurFlowName(name);
workflowsTable?.updateCurWorkflowID(id);
if (id == null) {
document.title = "ComfyUI";
return;
}
if (getWorkflowIdInUrlHash()) {
const newUrlHash = generateUrlHashWithFlowId(id);
window.location.hash = newUrlHash;
@@ -198,7 +203,13 @@ export default function App() {
setRoute("root");
};
const loadWorkflowID = (id: string) => {
const loadWorkflowID = (id: string | null) => {
// curID null is when you deleted current workflow
if (id === null) {
setCurFlowIDAndName(null, "");
app.graph.clear();
return;
}
const autoSaveEnabled = userSettingsTable?.getSetting("autoSave") ?? true;
if (autoSaveEnabled || !isDirty) {
loadWorkflowIDImpl(id);
@@ -373,6 +384,14 @@ export default function App() {
fileInput?.addEventListener("change", fileInputListener);
const handleBeforeUnload = async (e: BeforeUnloadEvent) => {
const autoSaveEnabled = userSettingsTable?.getSetting("autoSave") ?? true;
const isDirty =
!!workflowsTable?.curWorkflow &&
checkIsDirtyImpl(workflowsTable?.curWorkflow);
if (!autoSaveEnabled && isDirty) {
e.preventDefault(); // For modern browsers
e.returnValue = "You have unsaved changes!"; // For older browsers
}
showDialog(
`Please save or discard your changes before leaving, or your changes will be lost.`,
[
@@ -392,14 +411,6 @@ export default function App() {
},
],
);
const autoSaveEnabled = userSettingsTable?.getSetting("autoSave") ?? true;
const isDirty =
!!workflowsTable?.curWorkflow &&
checkIsDirtyImpl(workflowsTable?.curWorkflow);
if (!autoSaveEnabled && isDirty) {
e.preventDefault(); // For modern browsers
e.returnValue = "You have unsaved changes"; // For older browsers
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
@@ -13,7 +13,7 @@ import {
Flex,
Tooltip,
} from "@chakra-ui/react";
import { useEffect, useState, useRef, useCallback } from "react";
import { useEffect, useState, useRef, useCallback, useContext } from "react";
import {
workflowsTable,
isFolder,
@@ -25,7 +25,7 @@ import {
IconPlus,
IconFolder,
} from "@tabler/icons-react";
import { RecentFilesContext } from "../WorkspaceContext";
import { RecentFilesContext, WorkspaceContext } from "../WorkspaceContext";
import RecentFilesDrawerMenu from "./RecentFilesDrawerMenu";
import { sortFileItem } from "../utils";
import WorkflowListItem from "./WorkflowListItem";
@@ -53,7 +53,7 @@ export default function RecentFilesDrawer({ onClose, onClickNewFlow }: Props) {
>([]);
const aloneFlowsAndFoldersRef = useRef<Array<Folder | Workflow>>([]);
const allFlowsRef = useRef<Array<Workflow>>([]);
const { loadWorkflowID } = useContext(WorkspaceContext);
const [selectedTag, setSelectedTag] = useState<string>();
const [multipleState, setMultipleState] = useState(false);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
@@ -119,6 +119,9 @@ export default function RecentFilesDrawer({ onClose, onClickNewFlow }: Props) {
const onDelete = useCallback(
async (id: string) => {
await workflowsTable?.deleteFlow(id);
if (workflowsTable?.curWorkflow?.id === id) {
loadWorkflowID?.(null);
}
await loadLatestWorkflows();
},
[selectedTag, debounceSearchValue],
+1 -1
View File
@@ -5,7 +5,7 @@ import { Folder, Workflow } from "./types/dbTypes";
export const WorkspaceContext = createContext<{
curFlowID: string | null;
onDuplicateWorkflow?: (flowID: string, newFlowName?: string) => void;
loadWorkflowID: (id: string) => void;
loadWorkflowID: (id: string | null) => void;
saveCurWorkflow: () => void;
discardUnsavedChanges: () => void;
isDirty: boolean;
+1 -1
View File
@@ -142,7 +142,7 @@ export default function DropdownTitle({ onClick }: { onClick?: () => void }) {
>
Save
</MenuItem>
<Tooltip label="This will revert current workflow to your last saved version. You will lose all changes made since your last save.">
<Tooltip label="Revert workflow to your last saved version. You will lose all changes made since your last save.">
<MenuItem
onClick={discardUnsavedChanges}
icon={<IconArrowBackUpDouble size={20} />}
+7 -3
View File
@@ -26,7 +26,11 @@ export class WorkflowsTable extends TableBase<Workflow> {
const instance = new WorkflowsTable();
return instance;
}
public updateCurWorkflowID(id: string) {
public updateCurWorkflowID(id: string | null) {
if (id == null) {
this._curWorkflow = null;
return;
}
this.get(id).then((w) => {
this._curWorkflow = w ?? null;
});
@@ -88,13 +92,13 @@ export class WorkflowsTable extends TableBase<Workflow> {
}
//update indexdb
await indexdb.workflows.update(id, newWorkflow);
//update curWorkflow ram
//update curWorkflow RAM
if (this._curWorkflow && this._curWorkflow.id === id) {
this._curWorkflow = newWorkflow;
}
await this.saveDiskDB();
// save to my_workflows/
if (input.name !== null || input.parentFolderID !== null) {
if (input.name != null || input.parentFolderID != null) {
// renamed file or moved file folder
await deleteJsonFileMyWorkflows(before);
await saveJsonFileMyWorkflows(after);