Skip to content

Commit

Permalink
Add file change indicator (#47)
Browse files Browse the repository at this point in the history
* can't edit asset name

* Update README.md

* search checks author

* version button exists

* version selection front-end done

* state included version

* bug fix, empty versions list

* i want more than 3 versions!!!!

* can no longer add assets with the same name

added error check for uploading of a new asset with the same name as one in the db. Also added user error display message

* Validation check added: asset name must be camelCase

* almost there, downloads versions, but idk what's downloaded

* support for new downloads json infrastructure

* file does not exist error fix

* VERSION DOWNLOAD DONE

* oopsies

* revert to main

* front end confirm window

* basic function front-end set up

* oopsies

* connections almost set up, but promise machine said 'nah fuck you'

* comment this out in case of problems

* hash todos

* add super basic function skeleton

* add in old code

* fix (1/3) ifFilesChanges correct return type

* added hash library

* fix (2/3) correct libary import

* fix (3/3) hash function correct return value

* pass in asset id

* update json type

* WORKS

* fixing lingering merge errors

* added my new stuff so that it works again

* add warning to both version change buttons

* remove assetName var

* clean PR

* Update "unsync" button with hashing check, format + cleanup

---------

Co-authored-by: Kyra Clark <[email protected]>
Co-authored-by: pojojojo21 <[email protected]>
Co-authored-by: Thomas Shaw <[email protected]>
  • Loading branch information
4 people authored Apr 29, 2024
1 parent 731ac83 commit 3128458
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 19 deletions.
27 changes: 27 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"electron-store": "^8.2.0",
"electron-updater": "^6.1.7",
"extract-zip": "^2.0.1",
"folder-hash": "^4.0.4",
"jszip": "^3.10.1",
"openapi-fetch": "^0.9.3",
"openapi-typescript": "^6.7.5",
Expand Down
49 changes: 43 additions & 6 deletions frontend/src/main/lib/local-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createWriteStream } from 'fs';
import { existsSync } from 'node:fs';
import fsPromises from 'node:fs/promises';
import path from 'node:path';
import { hashElement } from 'folder-hash'

import { DownloadedEntry, Version } from '../../types/ipc';
import { getAuthToken } from './authentication';
Expand Down Expand Up @@ -38,18 +39,45 @@ export function getDownloadedVersionByID(asset_id: string) {

function setDownloadedVersion(
asset_id: string,
{ semver, folderName }: Omit<DownloadedEntry, 'asset_id'>,
{ semver, folderName, folderHash }: Omit<DownloadedEntry, 'asset_id'>,
) {
const downloads = store.get('downloadedAssetVersions');

const newDownloads = [
...downloads.filter(({ asset_id: id }) => id !== asset_id),
{ asset_id, semver, folderName },
{ asset_id, semver, folderName, folderHash },
] satisfies DownloadedEntry[];

store.set('downloadedAssetVersions', newDownloads);
}

export async function getFolderHash(filePath: string): Promise<string>{
return hashElement(filePath)
.then(hash => {
return hash['hash'].toString()
})
.catch(error => {
console.error('hashing failed:', error);
});
}

export async function ifFilesChanged(asset_id: string): Promise<boolean> {
// compare current with saved hash
const downloads = await getDownloadedVersions()
const saved_asset = downloads.find((a) => asset_id === a.asset_id)
if (saved_asset === undefined) {
return true
}
const saved_hash = saved_asset.folderHash

// what is the current hash
const folderName = saved_asset.folderName
const folderPath = path.join(getDownloadFolder(), folderName);
const current_hash = await getFolderHash(folderPath)

return (current_hash !== saved_hash)
}

/**
* Should be run after POST /api/v1/assets/ to create an empty folder for the asset
*/
Expand All @@ -68,7 +96,10 @@ export async function createInitialVersion({
await fsPromises.mkdir(folderPath, { recursive: true });

console.log('adding to store');
setDownloadedVersion(asset_id, { semver: null, folderName });

// hash new folder
const folderHash = await getFolderHash(folderPath)
setDownloadedVersion(asset_id, { semver: null, folderName, folderHash });
}

export async function openFolder(asset_id: string) {
Expand Down Expand Up @@ -156,9 +187,12 @@ export async function commitChanges(asset_id: string, message: string, is_major:
throw new Error(`Failed to upload zip file for asset ${asset_id}`);
}

// Update saved hash
const folderHash = await getFolderHash(sourceFolder)

// Update store with currently downloaded version
const { semver } = result.data as Version;
setDownloadedVersion(asset_id, { semver, folderName });
setDownloadedVersion(asset_id, { semver, folderName, folderHash });

// Clean up the zip file
await fsPromises.rm(zipFilePath);
Expand Down Expand Up @@ -208,15 +242,18 @@ export async function downloadVersion({ asset_id, semver }: { asset_id: string;
// previously had semver in here but probably not necessary
// const folderName = `${asset_name}_${semver}_${asset_id.substring(0, 8)}/`;
const folderName = `${asset_name}_${asset_id.substring(0, 8)}/`;
const folderPath = path.join(getDownloadFolder(), folderName)

// remove old copy of folder
await fsPromises.rm(path.join(getDownloadFolder(), folderName), { force: true, recursive: true });
await extract(zipFilePath, { dir: path.join(getDownloadFolder(), folderName) });
await extract(zipFilePath, { dir: folderPath });

console.log('removing zip file...');
await fsPromises.rm(zipFilePath);

const folderHash = await getFolderHash(folderPath)
console.log('marking as stored!');
setDownloadedVersion(asset_id, { semver, folderName });
setDownloadedVersion(asset_id, { semver, folderName, folderHash });

console.log('we made it! check', getDownloadFolder());
return getDownloadedVersions();
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/main/message-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
createInitialVersion,
downloadVersion,
getDownloadedVersions,
ifFilesChanged,
openFolder,
unsyncAsset,
} from './lib/local-assets';
Expand Down Expand Up @@ -38,6 +39,9 @@ const messageHandlers: MessageHandlers = {
await commitChanges(asset_id, message, is_major);
return { ok: true };
},
'assets:files-changed': async (_, { asset_id }) => {
return { ok: true, ifChanged: await ifFilesChanged(asset_id) };
},
'assets:open-folder': async (_, { asset_id }) => {
console.log(`Opening folder for ${asset_id}`);
await openFolder(asset_id);
Expand Down
34 changes: 29 additions & 5 deletions frontend/src/renderer/src/components/metadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const thomasImage =

export default function Metadata() {
const { asset, versions } = useSelectedAsset();
const { downloadedVersions, syncAsset, unsyncAsset, isValidating } = useDownloads();
const { downloadedVersions, syncAsset, unsyncAsset, ifFilesChanged, isValidating } =
useDownloads();
const refetchSearch = useAssetsSearchRefetch();

// versions for showing asset versions
Expand Down Expand Up @@ -112,6 +113,20 @@ export default function Metadata() {
});
};

const onVersionClick = async (asset: Asset, semver: string) => {
if (!asset) return;

if (
(await ifFilesChanged(asset.id)) &&
!confirm(
`${asset.asset_name} has uncommitted changes. You will lose your work if you switch versions. \nPress OK to continue without saving.`,
)
)
return;

syncAsset({ uuid: asset.id, asset_name: asset.asset_name, semver });
};

if (!asset) {
return (
<div className="flex h-full flex-col px-6 py-4">
Expand Down Expand Up @@ -275,11 +290,22 @@ export default function Metadata() {
asset={asset}
allVersions={allVersions}
currentVersion={currentVersion}
setVersion={(semver) => onVersionClick(asset, semver)}
/>
<button
className="btn btn-ghost btn-sm flex w-full flex-row flex-nowrap items-center justify-start gap-2 text-sm font-normal"
disabled={isValidating}
onClick={() => unsyncAsset({ uuid: asset.id, assetName: asset.asset_name })}
onClick={async () => {
if (
(await ifFilesChanged(asset.id)) &&
!confirm(
`${asset.asset_name} has uncommitted changes. You will lose your work if you continue. \nPress OK to unsync.`,
)
) {
return;
}
unsyncAsset({ uuid: asset.id, assetName: asset.asset_name });
}}
>
<MdSyncDisabled className="h-5 w-5" />
Unsync
Expand Down Expand Up @@ -322,9 +348,7 @@ export default function Metadata() {
<div className="chat-bubble- chat-bubble">
<button
className={`badge -ml-1 mr-1 font-mono hover:opacity-90 active:scale-90 ${currentVersion?.semver === semver ? 'ring-2 ring-primary' : ''}`}
onClick={() => {
syncAsset({ uuid: asset.id, asset_name: asset.asset_name, semver });
}}
onClick={() => onVersionClick(asset, semver)}
>
{semver}
</button>{' '}
Expand Down
10 changes: 3 additions & 7 deletions frontend/src/renderer/src/components/version-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import useDownloads from '@renderer/hooks/use-downloads';
import { Asset } from '@renderer/types';
import { MdCommit } from 'react-icons/md';
import { DownloadedEntry } from 'src/types/ipc';

const VersionSelector = ({
asset,
allVersions,
currentVersion,
setVersion,
}: {
asset: Asset;
allVersions: string[];
currentVersion?: DownloadedEntry;
setVersion: (semver: string) => void;
}) => {
const { syncAsset } = useDownloads();

if (allVersions.length === 0) {
return (
<label
Expand Down Expand Up @@ -45,9 +43,7 @@ const VersionSelector = ({
className={
version === currentVersion?.semver ? 'rounded-lg bg-base-300 bg-opacity-60' : ''
}
onClick={() =>
syncAsset({ uuid: asset.id, asset_name: asset.asset_name, semver: version })
}
onClick={() => setVersion(version)}
>
v{version}
</button>
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/renderer/src/hooks/use-downloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export default function useDownloads() {
});
}

function ifFilesChanged(asset_id: string) {
return window.api.ipc('assets:files-changed', {asset_id}).then((response) => response.ifChanged);
}

const commitChanges = useCallback(
async (opts: {
asset_id: string;
Expand Down Expand Up @@ -107,6 +111,7 @@ export default function useDownloads() {
syncAsset,
unsyncAsset,
commitChanges,
ifFilesChanged,
openFolder,
mutate,
};
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/types/ipc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type DownloadedEntry = {
// null semver means no associated version
semver: string | null;
folderName: string;
folderHash: string;
};

type GriddleIpcSchema = {
Expand Down Expand Up @@ -41,6 +42,10 @@ type GriddleIpcSchema = {
request: { asset_id: string; message: string; is_major: boolean };
response: { ok: boolean };
};
'assets:files-changed': {
request: { asset_id: string };
response: { ok: boolean, ifChanged: boolean };
};
'assets:open-folder': {
request: { asset_id: string };
response: { ok: boolean };
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/types/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,14 @@ export interface operations {
/** @description Successful Response */
200: {
content: {
'application/json': unknown;
'application/json': {
asset_id: string,
date: string,
file_key: string,
author_pennkey: string,
semver: string,
message: string
};
};
};
/** @description Not found */
Expand Down

0 comments on commit 3128458

Please sign in to comment.